unit Myc.Data.Keyword;

interface

uses
    System.SysUtils,
    System.Generics.Collections,
    System.Generics.Defaults,
    System.SyncObjs;

type
    // The runtime representation of an interned keyword
    IKeyword = interface
        {$region 'private'}
        function GetIdx: Integer;
        function GetName: string;
        {$endregion}
        property Idx: Integer read GetIdx;
        property Name: string read GetName;
    end;

    // Factory and registry for all keywords (Flyweight Pattern)
    TKeywordRegistry = record
    strict private
        type
            TKeyword = class(TInterfacedObject, IKeyword)
            private
                FName: string;
                FIdx: Integer;
                function GetName: string;
                function GetIdx: Integer;
            public
                constructor Create(const AName: string; AIdx: Integer);
            end;
    strict private
        class var
            FLock: TSpinLock;
            FRegistry: TDictionary<string, Integer>;
            FReverseMap: TList<IKeyword>;
        class constructor Create;
        class destructor Destroy;
    public
        // Gets or creates the interned keyword for the given name.
        class function Intern(const AName: string): IKeyword; static;
        // Gets the name for a given keyword index.
        class function GetName(AIdx: Integer): string; static;
    end;

    // Defines a mapping from Keywords to a generic value T
    IKeywordMapping<T> = interface
        {$region 'private'}
        function GetItems(Idx: Integer): TPair<IKeyword, T>;
        function GetFields(const Key: IKeyword): T;
        function GetCount: Integer;
        {$endregion}
        // Finds the 0-based index for a given keyword. Returns -1 if not found.
        function IndexOf(const Key: IKeyword): Integer;

        // Access value by Keyword (raises exception if not found)
        property Fields[const Key: IKeyword]: T read GetFields;
        // Access Key-Value pair by Index (0..Count-1)
        property Items[Idx: Integer]: TPair<IKeyword, T> read GetItems; default;
        property Count: Integer read GetCount;
    end;

    // Factory for creating IKeywordMapping instances
    TKeywordMappingRegistry<T> = record
    strict private
        // Implementation class for IKeywordMapping
        type
            TKeywordMapping = class(TInterfacedObject, IKeywordMapping<T>)
            private
                FMap: TArray<Integer>;
                FFields: TArray<TPair<IKeyword, T>>;
                FFirst, FLast: Integer;

                function GetItems(Idx: Integer): TPair<IKeyword, T>;
                function GetFields(const Key: IKeyword): T;
                function GetCount: Integer;
            public
                // Expects AFields to be in the desired (canonical) order
                constructor Create(const AFields: TArray<TPair<IKeyword, T>>);
                function IndexOf(const Key: IKeyword): Integer;
            end;
    strict private
        // Cache implementation
        class var
            FLock: TSpinLock;
            FRegistry: TDictionary<TArray<Integer>, IKeywordMapping<T>>;
        class constructor Create;
        class destructor Destroy;

    public
        // Creates or gets the cached keyword mapping.
        class function Intern(const AFields: TArray<TPair<IKeyword, T>>): IKeywordMapping<T>; static;
    end;

    TKeywordMapping<T> = class(TInterfacedObject, IKeywordMapping<T>)
    private
        FFields: TArray<TPair<IKeyword, T>>;

        // IKeywordMapping implementation
        function GetItems(Idx: Integer): TPair<IKeyword, T>;
        function GetFields(const Key: IKeyword): T;
        function GetCount: Integer;
    public
        constructor Create(const AFields: TArray<TPair<IKeyword, T>>);
        function IndexOf(const Key: IKeyword): Integer;
    end;

implementation

uses
    Winapi.Windows;

{ TKeywordRegistry.TKeyword }

constructor TKeywordRegistry.TKeyword.Create(const AName: string; AIdx: Integer);
begin
    inherited Create;
    FName := AName;
    FIdx := AIdx;
end;

function TKeywordRegistry.TKeyword.GetIdx: Integer;
begin
    Result := FIdx;
end;

function TKeywordRegistry.TKeyword.GetName: string;
begin
    Result := FName;
end;

{ TKeywordRegistry }

class constructor TKeywordRegistry.Create;
begin
    FRegistry := TDictionary<string, Integer>.Create;
    FReverseMap := TList<IKeyword>.Create;
    FLock := TSpinLock.Create(False);
end;

class destructor TKeywordRegistry.Destroy;
begin
    FRegistry.Free;
    FReverseMap.Free;
end;

class function TKeywordRegistry.Intern(const AName: string): IKeyword;
var
    idx: Integer;
begin
    FLock.Enter;
    try
        if not FRegistry.TryGetValue(AName, idx) then
        begin
            idx := FReverseMap.Count;
            Result := TKeyword.Create(AName, idx);
            FReverseMap.Add(Result);
            FRegistry.Add(AName, idx);
        end
        else
        begin
            Result := FReverseMap[idx];
        end;
    finally
        FLock.Exit;
    end;
end;

class function TKeywordRegistry.GetName(AIdx: Integer): string;
begin
    if (AIdx >= 0) and (AIdx < FReverseMap.Count) then
        Result := FReverseMap[AIdx].Name
    else
        Result := '';
end;

{ TKeywordMappingRegistry<T>.TKeywordMapping }

constructor TKeywordMappingRegistry<T>.TKeywordMapping.Create(const AFields: TArray<TPair<IKeyword, T>>);
var
    i: Integer;
    keyIdx: Integer;
begin
    inherited Create;
    FFields := AFields;

    if Length(FFields) = 0 then
    begin
        FFirst := 0;
        FLast := -1;
        FMap := nil;
        Exit;
    end;

    FFirst := FFields[0].Key.Idx;
    FLast := FFirst;

    for i := 1 to High(FFields) do
    begin
        keyIdx := FFields[i].Key.Idx;
        if keyIdx < FFirst then
            FFirst := keyIdx;
        if keyIdx > FLast then
            FLast := keyIdx;
    end;

    SetLength(FMap, FLast - FFirst + 1);
    for i := 0 to High(FMap) do
        FMap[i] := -1;

    for i := 0 to High(FFields) do
    begin
        keyIdx := FFields[i].Key.Idx;
        FMap[keyIdx - FFirst] := i;
    end;
end;

function TKeywordMappingRegistry<T>.TKeywordMapping.GetCount: Integer;
begin
    Result := Length(FFields);
end;

// Interface method for Items property
function TKeywordMappingRegistry<T>.TKeywordMapping.GetItems(Idx: Integer): TPair<IKeyword, T>;
begin
    Result := FFields[Idx];
end;

// Interface method for Fields property
function TKeywordMappingRegistry<T>.TKeywordMapping.GetFields(const Key: IKeyword): T;
var
    idx: Integer;
begin
    idx := IndexOf(Key);
    if idx < 0 then
        raise EArgumentException.CreateFmt('Keyword "%s" not found in mapping.', [Key.Name]);
    Result := FFields[idx].Value;
end;

function TKeywordMappingRegistry<T>.TKeywordMapping.IndexOf(const Key: IKeyword): Integer;
var
    keyIdx, mapIdx: Integer;
begin
    keyIdx := Key.Idx;
    if (keyIdx < FFirst) or (keyIdx > FLast) then
        Exit(-1);

    mapIdx := keyIdx - FFirst;
    if (mapIdx < Length(FMap)) then
        Result := FMap[mapIdx]
    else
        Result := -1;
end;

{ TKeywordMappingRegistry<T> }

class constructor TKeywordMappingRegistry<T>.Create;
begin
    FRegistry := TDictionary<TArray<Integer>, IKeywordMapping<T>>.Create(TEqualityComparer<TArray<Integer>>.Default);
    FLock := TSpinLock.Create(False);
end;

class destructor TKeywordMappingRegistry<T>.Destroy;
begin
    FRegistry.Free;
end;

class function TKeywordMappingRegistry<T>.Intern(const AFields: TArray<TPair<IKeyword, T>>): IKeywordMapping<T>;
var
    signature: TArray<Integer>;
    i: Integer;
begin
    SetLength(signature, Length(AFields));
    for i := 0 to High(AFields) do
        signature[i] := AFields[i].Key.Idx;

    FLock.Enter;
    try
        if not FRegistry.TryGetValue(signature, Result) then
        begin
            Result := TKeywordMapping.Create(AFields);
            FRegistry.Add(signature, Result);
        end;
    finally
        FLock.Exit;
    end;
end;

{ TKeywordMapping }

constructor TKeywordMapping<T>.Create(const AFields: TArray<TPair<IKeyword, T>>);
begin
    inherited Create;
    FFields := AFields;
end;

function TKeywordMapping<T>.GetCount: Integer;
begin
    Result := Length(FFields);
end;

function TKeywordMapping<T>.GetFields(const Key: IKeyword): T;
var
    idx: Integer;
begin
    idx := IndexOf(Key);
    if idx < 0 then
        raise EArgumentException.CreateFmt('Field "%s" not found in dynamic record.', [Key.Name]);
    Result := FFields[idx].Value;
end;

function TKeywordMapping<T>.GetItems(Idx: Integer): TPair<IKeyword, T>;
begin
    Result := FFields[Idx];
end;

function TKeywordMapping<T>.IndexOf(const Key: IKeyword): Integer;
var
    i: Integer;
begin
    // Linear search (O(n)). Fast enough for typical records.
    // Keyword comparison is integer-based (Idx), so this is efficient.
    for i := 0 to High(FFields) do
    begin
        if FFields[i].Key.Idx = Key.Idx then
        begin
            Result := i;
            exit;
        end;
    end;
    Result := -1;
end;

end.
