import { Effect } from "effect";

import type { StorageFailure } from "./fuma-runtime";
import type { Owner } from "./ids";

export type PluginStorageSchema = {
  readonly Type: object;
};

export type PluginStorageSchemaType<TSchema extends PluginStorageSchema> = TSchema["Type"];

export type PluginStorageIndexField<TData extends object> = Extract<keyof TData, string>;

export type PluginStorageIndexSpec<TData extends object> =
  | PluginStorageIndexField<TData>
  | readonly PluginStorageIndexField<TData>[];

export type PluginStorageRuntimeIndexSpec = string | readonly string[];

export interface PluginStorageRuntimeCollectionDefinition {
  readonly name: string;
  readonly schema: PluginStorageSchema;
  readonly indexes: readonly PluginStorageRuntimeIndexSpec[];
}

export interface PluginStorageCollectionDefinition<
  TName extends string = string,
  TData extends object = Record<string, unknown>,
  TIndexes extends readonly PluginStorageIndexSpec<TData>[] =
    readonly PluginStorageIndexSpec<TData>[],
> extends PluginStorageRuntimeCollectionDefinition {
  readonly name: TName;
  readonly schema: PluginStorageSchema;
  readonly indexes: TIndexes;
}

export type PluginStorageConfig = Readonly<
  Record<string, PluginStorageRuntimeCollectionDefinition>
>;

export const definePluginStorageCollection = <
  const TName extends string,
  const TSchema extends PluginStorageSchema,
  const TIndexes extends readonly PluginStorageIndexSpec<PluginStorageSchemaType<TSchema>>[] =
    readonly [],
>(
  name: TName,
  schema: TSchema,
  options?: {
    readonly indexes?: TIndexes;
  },
): PluginStorageCollectionDefinition<TName, PluginStorageSchemaType<TSchema>, TIndexes> => ({
  name,
  schema,
  indexes: (options?.indexes ?? []) as TIndexes,
});

export type PluginStorageCollectionData<TDefinition> =
  TDefinition extends PluginStorageCollectionDefinition<infer _Name, infer TData, infer _Indexes>
    ? TData
    : never;

export type PluginStorageIndexFields<TIndexes> = TIndexes extends readonly (infer TIndex)[]
  ? TIndex extends readonly (infer TField)[]
    ? Extract<TField, string>
    : Extract<TIndex, string>
  : never;

export type PluginStorageCollectionIndexedField<TDefinition> =
  TDefinition extends PluginStorageCollectionDefinition<infer _Name, infer _Data, infer TIndexes>
    ? PluginStorageIndexFields<TIndexes>
    : never;

export interface PluginStorageWhereFilter<TValue> {
  readonly eq?: TValue;
  readonly in?: readonly TValue[];
  readonly gt?: TValue;
  readonly gte?: TValue;
  readonly lt?: TValue;
  readonly lte?: TValue;
}

export type PluginStorageWhereValue<TValue> = TValue | PluginStorageWhereFilter<TValue>;

export type PluginStorageCollectionWhere<TDefinition> = {
  readonly [TField in PluginStorageCollectionIndexedField<TDefinition>]?: PluginStorageWhereValue<
    TField extends keyof PluginStorageCollectionData<TDefinition>
      ? PluginStorageCollectionData<TDefinition>[TField]
      : never
  >;
};

export interface PluginStorageCollectionOrderBy<TDefinition> {
  readonly field: PluginStorageCollectionIndexedField<TDefinition>;
  readonly direction?: "asc" | "desc";
}

export interface PluginStorageKeyInput {
  readonly collection: string;
  readonly key: string;
}

export interface PluginStorageScopedKeyInput extends PluginStorageKeyInput {
  readonly owner: Owner;
}

export interface PluginStorageListInput {
  readonly collection: string;
  readonly keyPrefix?: string;
}

export interface PluginStoragePutInput extends PluginStorageScopedKeyInput {
  readonly data: unknown;
}

export interface PluginStoragePutManyEntry extends PluginStorageKeyInput {
  readonly data: unknown;
}

export interface PluginStoragePutManyInput {
  readonly owner: Owner;
  readonly entries: readonly PluginStoragePutManyEntry[];
}

export interface PluginStorageRemoveManyInput {
  readonly owner: Owner;
  readonly entries: readonly PluginStorageKeyInput[];
}

export interface PluginStorageCollectionKeyInput {
  readonly key: string;
}

export interface PluginStorageCollectionScopedKeyInput extends PluginStorageCollectionKeyInput {
  readonly owner: Owner;
}

export interface PluginStorageCollectionListInput {
  readonly keyPrefix?: string;
}

export interface PluginStorageCollectionPutInput<
  TData extends object,
> extends PluginStorageCollectionScopedKeyInput {
  readonly data: TData;
}

export interface PluginStorageCollectionQueryInput<TDefinition> {
  readonly keyPrefix?: string;
  readonly where?: PluginStorageCollectionWhere<TDefinition>;
  readonly orderBy?: readonly PluginStorageCollectionOrderBy<TDefinition>[];
  readonly limit?: number;
  readonly offset?: number;
}

export interface PluginStorageEntry<T = unknown> {
  readonly id: string;
  readonly owner: Owner;
  readonly pluginId: string;
  readonly collection: string;
  readonly key: string;
  readonly data: T;
  readonly createdAt: Date;
  readonly updatedAt: Date;
}

export interface PluginStorageCollectionFacade<
  TDefinition extends PluginStorageCollectionDefinition = PluginStorageCollectionDefinition,
> {
  readonly get: (
    input: PluginStorageCollectionKeyInput,
  ) => Effect.Effect<
    PluginStorageEntry<PluginStorageCollectionData<TDefinition>> | null,
    StorageFailure
  >;
  readonly getForOwner: (
    input: PluginStorageCollectionScopedKeyInput,
  ) => Effect.Effect<
    PluginStorageEntry<PluginStorageCollectionData<TDefinition>> | null,
    StorageFailure
  >;
  readonly list: (
    input?: PluginStorageCollectionListInput,
  ) => Effect.Effect<
    readonly PluginStorageEntry<PluginStorageCollectionData<TDefinition>>[],
    StorageFailure
  >;
  readonly put: (
    input: PluginStorageCollectionPutInput<PluginStorageCollectionData<TDefinition>>,
  ) => Effect.Effect<PluginStorageEntry<PluginStorageCollectionData<TDefinition>>, StorageFailure>;
  readonly query: (
    input?: PluginStorageCollectionQueryInput<TDefinition>,
  ) => Effect.Effect<
    readonly PluginStorageEntry<PluginStorageCollectionData<TDefinition>>[],
    StorageFailure
  >;
  readonly count: (
    input?: Omit<PluginStorageCollectionQueryInput<TDefinition>, "orderBy" | "limit" | "offset">,
  ) => Effect.Effect<number, StorageFailure>;
  readonly remove: (
    input: PluginStorageCollectionScopedKeyInput,
  ) => Effect.Effect<void, StorageFailure>;
}

export interface PluginStorageFacade {
  readonly collection: <const TDefinition extends PluginStorageCollectionDefinition>(
    definition: TDefinition,
  ) => PluginStorageCollectionFacade<TDefinition>;
  readonly get: <T = unknown>(
    input: PluginStorageKeyInput,
  ) => Effect.Effect<PluginStorageEntry<T> | null, StorageFailure>;
  readonly getForOwner: <T = unknown>(
    input: PluginStorageScopedKeyInput,
  ) => Effect.Effect<PluginStorageEntry<T> | null, StorageFailure>;
  readonly list: <T = unknown>(
    input: PluginStorageListInput,
  ) => Effect.Effect<readonly PluginStorageEntry<T>[], StorageFailure>;
  readonly put: <T = unknown>(
    input: PluginStoragePutInput,
  ) => Effect.Effect<PluginStorageEntry<T>, StorageFailure>;
  readonly putMany: (input: PluginStoragePutManyInput) => Effect.Effect<void, StorageFailure>;
  readonly remove: (input: PluginStorageScopedKeyInput) => Effect.Effect<void, StorageFailure>;
  readonly removeMany: (input: PluginStorageRemoveManyInput) => Effect.Effect<void, StorageFailure>;
}

export const pluginStorageId = (input: {
  readonly pluginId: string;
  readonly collection: string;
  readonly key: string;
}): string => JSON.stringify([input.pluginId, input.collection, input.key]);
