import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { isStageCategory } from "../db/pipelineStage";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  DuplicateStageNameError,
  InvalidCategoryError,
  InvalidNameError,
  StageNotFoundError,
} from "../lib/errors.generated";
import { loadPipelineLockError } from "../lib/pipelineLock";

export interface UpdatePipelineStageInput {
  stageId: string;
  name?: string;
  category?: string;
  color?: string | null;
}

const STAGE_OWN_KEYS = new Set(["stageId", "name", "category", "color"]);

// Module-managed PipelineStage columns that must never be writable through the
// extension-field pass-through — position changes go through ReorderPipelineStage.
const STAGE_RESERVED_KEYS = new Set(["id", "pipelineId", "position", "createdAt", "updatedAt"]);

export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdatePipelineStageInput & Partial<CF>,
  ctx: CommandContext,
) {
  void ctx;

  // Consumer extension fields (declared via defineModule's pipelineStage.fields)
  // pass through to the update untouched — except reserved model columns.
  const customFields: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
    if (!STAGE_OWN_KEYS.has(key) && !STAGE_RESERVED_KEYS.has(key)) {
      customFields[key] = value;
    }
  }

  const stage = await db
    .selectFrom("PipelineStage")
    .selectAll()
    .where("id", "=", input.stageId)
    .forUpdate()
    .executeTakeFirst();

  if (!stage) {
    return err(new StageNotFoundError(input.stageId));
  }

  const lockError = await loadPipelineLockError(db, stage.pipelineId);
  if (lockError) {
    return err(lockError);
  }

  if (input.name !== undefined && !input.name.trim()) {
    return err(new InvalidNameError(input.name));
  }

  if (input.name !== undefined && input.name !== stage.name) {
    const duplicate = await db
      .selectFrom("PipelineStage")
      .selectAll()
      .where("pipelineId", "=", stage.pipelineId)
      .where("name", "=", input.name)
      .executeTakeFirst();

    if (duplicate && duplicate.id !== stage.id) {
      return err(new DuplicateStageNameError(input.name));
    }
  }

  if (input.category !== undefined && !isStageCategory(input.category)) {
    return err(new InvalidCategoryError(input.category));
  }

  const update: Record<string, unknown> = {
    ...customFields,
    updatedAt: new Date(),
  };

  if (input.name !== undefined) update.name = input.name;
  if (input.category !== undefined) update.category = input.category;
  if (input.color !== undefined) update.color = input.color;

  const updatedStage = await db
    .updateTable("PipelineStage")
    .set(update)
    .where("id", "=", input.stageId)
    .returningAll()
    .executeTakeFirst();

  return ok({ stage: updatedStage ?? { ...stage, ...update } });
}
