import { types as T } from "./mod.ts";
import { EmVer } from "./emver-lite/mod.ts";
import { matches } from "./dependencies.ts";
import { LegacyExpectedExports as ExpectedExports } from "./types.ts";

export class Migration2<Version extends string> {
  version: Version;
  up: (
    effects: T.Effects,
  ) => Promise<T.MigrationRes>;
  down: (
    effects: T.Effects,
  ) => Promise<T.MigrationRes>;
  constructor(options: {
    version: Version;
    up: (
      effects: T.Effects,
    ) => Promise<T.MigrationRes>;
    down: (
      effects: T.Effects,
    ) => Promise<T.MigrationRes>;
  }) {
    this.version = options.version;
    this.up = options.up;
    this.down = options.down;
  }
}
export class MigrationMapping2<Migrations extends Migration2<string>[]> {
  constructor(
    readonly migrations: Migrations,
  ) {
  }
}

export type MigrationFn<version extends string, type extends "up" | "down"> = (
  effects: T.Effects,
) => Promise<T.MigrationRes> & { _type: type; _version: version };

export function migrationFn<version extends string, type extends "up" | "down">(
  fn: (
    effects: T.Effects,
  ) => Promise<T.MigrationRes>,
): MigrationFn<version, type> {
  return fn as MigrationFn<version, type>;
}

export interface Migration<version extends string> {
  up: MigrationFn<version, "up">;
  down: MigrationFn<version, "down">;
}

export type MigrationMapping<versions extends string> = {
  [version in versions]: Migration<version>;
};

export function fromMapping<versions extends string>(
  migrations: MigrationMapping<versions>,
  currentVersion: string,
): ExpectedExports.migration {
  const directionShape = matches.literals("from", "to");
  return async (
    effects: T.Effects,
    version: string,
    direction?: unknown,
  ) => {
    if (!directionShape.test(direction)) {
      return { error: 'Must specify arg "from" or "to".' };
    }

    let configured = true;

    const current = EmVer.parse(currentVersion);
    const other = EmVer.parse(version);

    const filteredMigrations = (Object.entries(migrations) as [
      keyof MigrationMapping<string>,
      Migration<string>,
    ][])
      .map(([version, migration]) => ({
        version: EmVer.parse(version),
        migration,
      })).filter(({ version }) =>
        version.greaterThan(other) && version.lessThanOrEqual(current)
      );

    const migrationsToRun = matches.matches(direction)
      .when("from", () =>
        filteredMigrations
          .sort((a, b) => a.version.compareForSort(b.version)) // low to high
          .map(({ migration }) => migration.up))
      .when("to", () =>
        filteredMigrations
          .sort((a, b) => b.version.compareForSort(a.version)) // high to low
          .map(({ migration }) => migration.down))
      .unwrap();

    for (const migration of migrationsToRun) {
      configured = (await migration(effects)).configured && configured;
    }

    return { result: { configured } };
  };
}
