import type { Transaction } from "../generated/kysely-tailordb";
import {
  InvalidEntityNameError,
  DuplicateEntityNameError,
  InvalidEntityScopeError,
  InvalidAuditableFieldsError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

interface AuditableFieldDef {
  fieldName: string;
  fieldType?: "scalar" | "richtext" | "array" | "relation" | "file" | "binary" | "computed";
}

export interface RegisterAuditableEntityInput {
  entityName: string;
  entityScope: string;
  auditableFields: (string | AuditableFieldDef)[];
}

const VALID_SCOPES = ["COMPANY_BOUND", "GLOBAL"] as const;

/**
 * Function: registerAuditableEntity
 *
 * Registers a new auditable entity type in the system with a given scope and
 * set of auditable fields. The entity name must be unique.
 */
export async function run(
  db: Transaction,
  input: RegisterAuditableEntityInput,
  _ctx: CommandContext,
) {
  const { entityName, entityScope, auditableFields } = input;

  // 1. Validate entityName non-empty
  if (!entityName || entityName.trim() === "") {
    return err(new InvalidEntityNameError(entityName ?? ""));
  }

  // 2. Validate entityScope
  if (!VALID_SCOPES.includes(entityScope as (typeof VALID_SCOPES)[number])) {
    return err(new InvalidEntityScopeError(entityScope ?? ""));
  }

  // 3. Validate auditableFields non-empty and all fields non-empty
  if (!auditableFields || auditableFields.length === 0) {
    return err(new InvalidAuditableFieldsError("empty array"));
  }

  const ELIGIBLE_FIELD_TYPES = ["scalar", "richtext", "array", "relation", "file"];
  const resolvedFields: { fieldName: string; fieldType?: string }[] = [];

  for (const field of auditableFields) {
    const name = typeof field === "string" ? field : field.fieldName;
    const fieldType = typeof field === "string" ? undefined : field.fieldType;

    if (!name || name.trim() === "") {
      return err(new InvalidAuditableFieldsError("empty field name"));
    }
    if (fieldType && !ELIGIBLE_FIELD_TYPES.includes(fieldType)) {
      return err(
        new InvalidAuditableFieldsError(`field '${name}' has ineligible type '${fieldType}'`),
      );
    }
    resolvedFields.push(fieldType ? { fieldName: name, fieldType } : { fieldName: name });
  }

  // 4. Check duplicate entityName (with forUpdate for uniqueness)
  const existing = await db
    .selectFrom("AuditableEntity")
    .selectAll()
    .where("entityName", "=", entityName)
    .forUpdate()
    .executeTakeFirst();

  if (existing) {
    return err(new DuplicateEntityNameError(entityName));
  }

  // 5. Insert AuditableEntity with auditableFields as JSON string (includes fieldType)
  const auditableEntity = await db
    .insertInto("AuditableEntity")
    .values({
      entityName,
      entityScope,
      auditableFields: JSON.stringify(resolvedFields),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  return ok({ auditableEntity: auditableEntity! });
}
