import type { StandardSchemaV1 } from "@standard-schema/spec";
import { runWithResolvedTracingContext } from "../tracing/execution.js";
import type { TracingPort } from "../tracing/index.js";

/**
 * Any Standard Schema compatible validator.
 */
export type StandardSchema = StandardSchemaV1<unknown, unknown>;

/**
 * Value or promise of that value.
 */
export type MaybePromise<T> = T | Promise<T>;

/**
 * Infer the parsed output type from a Standard Schema.
 */
export type InferSchemaOutput<T extends StandardSchemaV1> =
  StandardSchemaV1.InferOutput<T>;

/**
 * Operational task definition created by `defineTask(...)`.
 */
export interface TaskDef<
  Name extends string = string,
  Input extends StandardSchema = StandardSchema,
  Ctx = unknown,
  Output = unknown,
> {
  /**
   * Discriminator for task definitions.
   */
  readonly kind: "task";
  /**
   * Stable task name used by CLIs and operational runners.
   */
  readonly name: Name;
  /**
   * Standard Schema input validator.
   */
  readonly input: Input;
  /**
   * Optional human-readable description for docs and tooling.
   */
  readonly description?: string;
  /**
   * Handle a parsed task input.
   */
  handle(
    args: TaskHandleArgs<TaskDef<Name, Input, Ctx, Output>, Ctx>,
  ): MaybePromise<Output>;
}

/**
 * Infer the parsed input type for an operational task.
 */
export type InferTaskInput<T extends TaskDef> =
  T["input"] extends StandardSchemaV1<unknown, infer Output> ? Output : never;

/**
 * Infer the result type for an operational task.
 */
export type InferTaskOutput<T extends TaskDef> =
  T extends TaskDef<string, StandardSchema, unknown, infer Output>
    ? Awaited<Output>
    : never;

/**
 * Arguments passed to a task handler.
 */
export interface TaskHandleArgs<T extends TaskDef, Ctx> {
  /**
   * Task definition being handled.
   */
  task: T;
  /**
   * Parsed task input.
   */
  input: InferTaskInput<T>;
  /** Handler context. */
  ctx: Ctx;
}

/**
 * Options for `defineTask(...)`.
 */
export interface DefineTaskOptions<
  Name extends string,
  Input extends StandardSchema,
  Ctx,
  Output,
> {
  /**
   * Standard Schema input validator.
   */
  input: Input;
  /**
   * Optional human-readable description for docs and tooling.
   */
  description?: string;
  /**
   * Handle a parsed task input.
   */
  handle(
    args: TaskHandleArgs<TaskDef<Name, Input, Ctx, Output>, Ctx>,
  ): MaybePromise<Output>;
}

/**
 * Options for one task run.
 */
export interface RunTaskOptions<Ctx> {
  /**
   * Raw task input. It is parsed with the task's Standard Schema before the
   * handler runs.
   */
  input: unknown;
  /** Handler context or factory resolved inside the task span. */
  ctx: Ctx | (() => MaybePromise<Ctx>);
  /** Runtime tracing port used before a lazy context factory runs. */
  tracing?: TracingPort;
}

/**
 * Arguments `beignet task run` passes to the app's `createTaskContext` and
 * `stopTaskContext` exports in `server/tasks.ts`.
 */
export interface TaskRunContextArgs<T extends TaskDef = TaskDef> {
  /**
   * Task definition being run.
   */
  task: T;
  /**
   * Stable task name being run.
   */
  taskName: string;
  /**
   * Schema-parsed task input.
   */
  input: unknown;
  /**
   * Tenant id or slug from `--tenant`, resolved by the app.
   */
  tenant?: string;
}

/**
 * Context-bound operational task helper factory.
 */
export interface Tasks<Ctx> {
  /**
   * Define a task with the bound context type.
   */
  defineTask<
    Name extends string,
    Input extends StandardSchema,
    Output = unknown,
  >(
    name: Name,
    options: DefineTaskOptions<Name, Input, Ctx, Output>,
  ): TaskDef<Name, Input, Ctx, Output>;
}

/**
 * Error thrown when task input validation fails.
 */
export class TaskValidationError extends Error {
  /**
   * Raw Standard Schema validation issues.
   */
  readonly issues: readonly StandardSchemaV1.Issue[];

  constructor(args: {
    name: string;
    issues: readonly StandardSchemaV1.Issue[];
  }) {
    super(
      `Task "${args.name}" input validation failed: ${formatIssues(args.issues)}`,
    );
    this.name = "TaskValidationError";
    this.issues = args.issues;
  }
}

function formatPath(path: StandardSchemaV1.Issue["path"]): string {
  if (!path || path.length === 0) return "";

  return path
    .map((segment) => {
      if (typeof segment === "number") return `[${segment}]`;
      const key = String(segment);
      return /^[A-Za-z_$][\w$]*$/.test(key)
        ? `.${key}`
        : `[${JSON.stringify(key)}]`;
    })
    .join("")
    .replace(/^\./, "");
}

function formatIssues(issues: readonly StandardSchemaV1.Issue[]): string {
  return issues
    .map((issue) => {
      const path = formatPath(issue.path);
      return path ? `${path}: ${issue.message}` : issue.message;
    })
    .join("; ");
}

async function parseInput<Schema extends StandardSchemaV1>(
  schema: Schema,
  input: unknown,
  options: { name: string },
): Promise<InferSchemaOutput<Schema>> {
  const result = await schema["~standard"].validate(input);

  if (result.issues?.length) {
    throw new TaskValidationError({
      name: options.name,
      issues: result.issues,
    });
  }

  return (result as { value: InferSchemaOutput<Schema> }).value;
}

function defineTaskImpl<
  Name extends string,
  Input extends StandardSchema,
  Ctx = unknown,
  Output = unknown,
>(
  name: Name,
  options: DefineTaskOptions<Name, Input, Ctx, Output>,
): TaskDef<Name, Input, Ctx, Output> {
  return {
    kind: "task",
    name,
    input: options.input,
    description: options.description,
    handle: options.handle as TaskDef<Name, Input, Ctx, Output>["handle"],
  };
}

/**
 * Validate and parse a task input with the task's Standard Schema.
 */
export async function parseTaskInput<T extends TaskDef>(
  task: T,
  input: unknown,
): Promise<InferTaskInput<T>> {
  return (await parseInput(task.input, input, {
    name: task.name,
  })) as InferTaskInput<T>;
}

/**
 * Parse input and run an operational task.
 */
export async function runTask<
  T extends TaskDef<string, StandardSchema, Ctx>,
  Ctx,
>(task: T, options: RunTaskOptions<Ctx>): Promise<InferTaskOutput<T>> {
  const traceAttributes = {
    "beignet.task.name": task.name,
  } as const;

  return await runWithResolvedTracingContext({
    tracing: options.tracing,
    ctx: options.ctx,
    operation: {
      name: `beignet.task ${task.name}`,
      type: "task",
      kind: "internal",
      attributes: traceAttributes,
      metricAttributes: traceAttributes,
    },
    run: async (ctx) => {
      const parsed = await parseTaskInput(task, options.input);
      return (await task.handle({
        task,
        input: parsed,
        ctx,
      })) as InferTaskOutput<T>;
    },
  });
}

/**
 * Define a task registry while preserving tuple inference.
 */
export function defineTasks<const Defs extends readonly TaskDef[]>(
  tasks: Defs,
): Defs {
  return tasks;
}

/**
 * Create task helper methods bound to an application context type.
 *
 * Call it once in `lib/tasks.ts`:
 *
 * ```ts
 * export const { defineTask } = createTasks<AppContext>();
 * ```
 */
export function createTasks<Ctx>(): Tasks<Ctx> {
  return {
    defineTask<Name extends string, Input extends StandardSchema, Output>(
      name: Name,
      options: DefineTaskOptions<Name, Input, Ctx, Output>,
    ) {
      return defineTaskImpl<Name, Input, Ctx, Output>(name, options);
    },
  };
}
