import type {
  ApprovalToken,
  ToolInvocation,
  VoiceToolDefinition,
} from "./types.ts";
import { shouldEscalateForLanguage } from "./scheduling-policy.ts";

export type PolicyToolName =
  | "verify_patient_identity"
  | "assess_escalation"
  | "find_reschedule_options"
  | "request_reschedule"
  | "create_callback"
  | "transfer_to_staff";

export interface PolicyToolResult {
  ok: boolean;
  action:
    | "continue"
    | "offer_slots"
    | "rescheduled"
    | "no_valid_slot"
    | "approval_required"
    | "callback_required"
    | "amd_write_failed"
    | "transfer_to_staff";
  message: string;
  data?: Record<string, unknown>;
}

export interface SchedulingToolPort {
  verifyPatientIdentity(args: Record<string, unknown>): Promise<PolicyToolResult>;
  findRescheduleOptions(args: Record<string, unknown>): Promise<PolicyToolResult>;
  requestReschedule(args: Record<string, unknown>, approval?: ApprovalToken): Promise<PolicyToolResult>;
  createCallback(args: Record<string, unknown>): Promise<PolicyToolResult>;
  transferToStaff(args: Record<string, unknown>): Promise<PolicyToolResult>;
}

export class VoicePolicyProxy {
  constructor(
    private readonly scheduling: SchedulingToolPort,
    private readonly writeApproval?: ApprovalToken,
  ) {}

  toolDefinitions(): VoiceToolDefinition[] {
    return [
      {
        name: "verify_patient_identity",
        description:
          "Verify a caller before any scheduling details are discussed. Requires legal name and DOB; caller phone is supplied by the server when omitted. Returns next_appointment when AdvancedMD has a future appointment.",
        parameters: objectSchema(
          {
            caller_number: { type: "string" },
            stated_legal_name: { type: "string" },
            stated_dob: { type: "string", description: "Date of birth in YYYY-MM-DD format." },
          },
          ["stated_legal_name", "stated_dob"],
        ),
      },
      {
        name: "assess_escalation",
        description: "Check whether the caller said something that must be handled by staff.",
        parameters: objectSchema({ text: { type: "string" } }, ["text"]),
      },
      {
        name: "find_reschedule_options",
        description:
          "Find safe same-provider, same-service replacement appointment slots after identity verification.",
        parameters: objectSchema(
          {
            patient_id: { type: "string" },
            appointment_id: { type: "string" },
          },
          ["patient_id", "appointment_id"],
        ),
      },
      {
        name: "request_reschedule",
        description:
          "Request an AdvancedMD appointment reschedule. Requires explicit runtime approval before writing.",
        parameters: objectSchema(
          {
            patient_id: { type: "string" },
            legal_name: { type: "string" },
            appointment_id: { type: "string" },
            current_provider_id: { type: "string" },
            current_service_code: { type: "string" },
            current_starts_at: { type: "string", description: "Current appointment start time in ISO-8601 format." },
            slot_provider_id: { type: "string" },
            slot_service_code: { type: "string" },
            slot_starts_at: { type: "string", description: "Replacement slot start time in ISO-8601 format." },
            slot_duration_minutes: { type: "number" },
          },
          [
            "patient_id",
            "legal_name",
            "appointment_id",
            "current_provider_id",
            "current_service_code",
            "current_starts_at",
            "slot_provider_id",
            "slot_service_code",
            "slot_starts_at",
            "slot_duration_minutes",
          ],
        ),
      },
      {
        name: "create_callback",
        description: "Create a staff callback task when self-service cannot continue.",
        parameters: objectSchema({ reason: { type: "string" } }, ["reason"]),
      },
      {
        name: "transfer_to_staff",
        description: "Transfer the live caller to staff.",
        parameters: objectSchema({ reason: { type: "string" } }, ["reason"]),
      },
    ];
  }

  async handleToolCall(call: ToolInvocation): Promise<PolicyToolResult> {
    switch (call.name as PolicyToolName) {
      case "verify_patient_identity":
        return this.scheduling.verifyPatientIdentity(call.arguments);
      case "assess_escalation": {
        const text = String(call.arguments.text ?? "");
        if (shouldEscalateForLanguage(text)) {
          return {
            ok: false,
            action: "transfer_to_staff",
            message: "Caller language requires staff handling.",
          };
        }
        return { ok: true, action: "continue", message: "No escalation language detected." };
      }
      case "find_reschedule_options":
        return this.scheduling.findRescheduleOptions(call.arguments);
      case "request_reschedule": {
        const result = await this.scheduling.requestReschedule(call.arguments, this.writeApproval);
        if (!this.writeApproval && result.action !== "transfer_to_staff") {
          return {
            ok: false,
            action: "approval_required",
            message: "AdvancedMD writes are disabled until Gautam approves the exact automation policy.",
            data: result.data,
          };
        }
        return result;
      }
      case "create_callback":
        return this.scheduling.createCallback(call.arguments);
      case "transfer_to_staff":
        return this.scheduling.transferToStaff(call.arguments);
      default:
        return {
          ok: false,
          action: "transfer_to_staff",
          message: `Unsupported tool call: ${call.name}`,
        };
    }
  }
}

function objectSchema(
  properties: Record<string, unknown>,
  required: string[],
): Record<string, unknown> {
  return {
    type: "object",
    properties,
    required,
    additionalProperties: false,
  };
}
