/**
 * Tipos do flow.json — copia de templates/schema-blocks.ts.
 *
 * NAO EDITAR DIRETO. Mantido em sincronia pela skill no momento da geracao.
 */

import { z } from "zod";

export const BlockId = z
  .string()
  .regex(/^b\d{3,}$/, "Block ID precisa ser 'b' + 3+ digitos");

export const VarName = z
  .string()
  .regex(/^[a-z_][a-z0-9_]*$/, "Nome de variavel: snake_case minusculo");

export const HexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/);

const TextBlock = z.object({
  id: BlockId,
  type: z.literal("text"),
  content: z.string().min(1),
  delay_ms: z.number().int().min(0).max(10000).optional(),
  next: BlockId.optional(),
});

const ImageBlock = z.object({
  id: BlockId,
  type: z.literal("image"),
  url: z.string().url(),
  alt: z.string(),
  next: BlockId.optional(),
});

const VideoBlock = z.object({
  id: BlockId,
  type: z.literal("video"),
  url: z.string().url(),
  autoplay: z.boolean().default(false),
  next: BlockId.optional(),
});

const InputTextBlock = z.object({
  id: BlockId,
  type: z.literal("input_text"),
  prompt: z.string(),
  placeholder: z.string().optional(),
  var: VarName,
  validation: z
    .object({
      min_length: z.number().int().optional(),
      max_length: z.number().int().optional(),
      regex: z.string().optional(),
    })
    .optional(),
  next: BlockId.optional(),
});

const InputEmailBlock = z.object({
  id: BlockId,
  type: z.literal("input_email"),
  prompt: z.string(),
  var: VarName,
  next: BlockId.optional(),
});

const InputPhoneBlock = z.object({
  id: BlockId,
  type: z.literal("input_phone"),
  prompt: z.string(),
  var: VarName,
  country_default: z.string().length(2).default("BR"),
  next: BlockId.optional(),
});

const ChoiceBlock = z.object({
  id: BlockId,
  type: z.literal("choice"),
  prompt: z.string(),
  var: VarName.optional(),
  options: z
    .array(
      z.object({
        label: z.string(),
        value: z.string(),
        next: BlockId,
      }),
    )
    .min(2),
});

const MultiChoiceBlock = z.object({
  id: BlockId,
  type: z.literal("multi_choice"),
  prompt: z.string(),
  var_array: VarName,
  options: z
    .array(z.object({ label: z.string(), value: z.string() }))
    .min(2),
  next: BlockId,
});

const ConditionBlock = z.object({
  id: BlockId,
  type: z.literal("condition"),
  rules: z
    .array(
      z.object({
        if: z.object({
          var: VarName,
          op: z.enum(["eq", "neq", "gt", "lt", "gte", "lte", "contains", "in"]),
          value: z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]),
        }),
        then: BlockId,
      }),
    )
    .min(1),
  else: BlockId,
});

const SetVarBlock = z.object({
  id: BlockId,
  type: z.literal("set_var"),
  var: VarName,
  value: z.union([z.string(), z.number(), z.boolean()]),
  next: BlockId,
});

const AiChatBlock = z.object({
  id: BlockId,
  type: z.literal("ai"),
  system_prompt: z.string().min(20),
  exit_intents: z
    .array(z.object({ intent: z.string(), next: BlockId }))
    .min(1),
  fallback: BlockId,
  max_turns: z.number().int().min(1).max(20).default(5),
  variables_to_extract: z.array(VarName).optional(),
});

const WebhookBlock = z.object({
  id: BlockId,
  type: z.literal("webhook"),
  url: z.string().url(),
  method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("POST"),
  headers: z.record(z.string()).optional(),
  payload: z.record(z.unknown()).optional(),
  on_success: BlockId,
  on_error: BlockId,
  timeout_ms: z.number().int().min(100).max(30000).default(5000),
});

const RedirectBlock = z.object({
  id: BlockId,
  type: z.literal("redirect"),
  url: z.string().url(),
  utm: z
    .object({
      source: z.string(),
      medium: z.string(),
      campaign: z.string(),
      content: z.string().optional(),
      term: z.string().optional(),
    })
    .optional(),
});

export const Block = z.discriminatedUnion("type", [
  TextBlock, ImageBlock, VideoBlock,
  InputTextBlock, InputEmailBlock, InputPhoneBlock,
  ChoiceBlock, MultiChoiceBlock,
  ConditionBlock, SetVarBlock,
  AiChatBlock, WebhookBlock, RedirectBlock,
]);

export type Block = z.infer<typeof Block>;

const Variable = z.object({
  name: VarName,
  type: z.enum(["string", "number", "boolean", "array"]),
  required: z.boolean().default(false),
  default: z.unknown().optional(),
});

const Theme = z.object({
  primary_color: HexColor,
  secondary_color: HexColor,
  bg_dark: HexColor.optional(),
  text_on_primary: HexColor.default("#FFFFFF"),
  avatar_letter: z.string().length(1).optional(),
  font: z.string().default("Inter"),
  layout: z.enum(["centered", "chat-classic", "fullscreen"]).default("centered"),
});

export const Flow = z.object({
  schema_version: z.literal(1),
  bot_name: z.string().min(1),
  bot_slug: z.string().regex(/^[a-z0-9-]+$/),
  archetype: z.enum(["vendedora", "diagnostico", "qualificador", "custom"]),
  start_block_id: BlockId,
  variables: z.array(Variable),
  theme: Theme,
  blocks: z.record(BlockId, Block),
  meta: z.object({
    created_at: z.string().datetime(),
    generator_version: z.string(),
    owner_email: z.string().email().optional(),
    source_page_url: z.string().url().optional(),
  }),
});

export type Flow = z.infer<typeof Flow>;

export type ValidationIssue = {
  level: "error" | "warning";
  block_id?: string;
  message: string;
};

export function validateFlowStructure(flow: Flow): ValidationIssue[] {
  const issues: ValidationIssue[] = [];
  const blockIds = new Set(Object.keys(flow.blocks));

  if (!blockIds.has(flow.start_block_id)) {
    issues.push({
      level: "error",
      message: `start_block_id '${flow.start_block_id}' nao existe`,
    });
  }

  const referencedIds = new Set<string>();
  const declaredVars = new Set(flow.variables.map((v) => v.name));
  const usedVars = new Set<string>();

  for (const [id, block] of Object.entries(flow.blocks)) {
    const refs: string[] = [];
    switch (block.type) {
      case "text": case "image": case "video":
      case "input_text": case "input_email": case "input_phone":
        if (block.next) refs.push(block.next); break;
      case "choice":
        block.options.forEach((o) => refs.push(o.next)); break;
      case "multi_choice": case "set_var":
        refs.push(block.next); break;
      case "condition":
        block.rules.forEach((r) => refs.push(r.then));
        refs.push(block.else); break;
      case "ai":
        block.exit_intents.forEach((i) => refs.push(i.next));
        refs.push(block.fallback); break;
      case "webhook":
        refs.push(block.on_success, block.on_error); break;
      case "redirect": break;
    }
    refs.forEach((r) => referencedIds.add(r));
    refs.forEach((r) => {
      if (!blockIds.has(r)) {
        issues.push({ level: "error", block_id: id, message: `aponta pra '${r}' inexistente` });
      }
    });

    if (block.type === "text" || block.type === "input_text" ||
        block.type === "input_email" || block.type === "input_phone") {
      const tpl = block.type === "text" ? block.content : block.prompt;
      for (const m of tpl.matchAll(/\{\{(\w+)\}\}/g)) usedVars.add(m[1]!);
    }
    if (block.type === "condition") block.rules.forEach((r) => usedVars.add(r.if.var));
    if (block.type === "set_var") declaredVars.add(block.var);
    if (block.type === "input_text" || block.type === "input_email" || block.type === "input_phone")
      declaredVars.add(block.var);
    if (block.type === "choice" && block.var) declaredVars.add(block.var);
    if (block.type === "multi_choice") declaredVars.add(block.var_array);
  }

  for (const id of blockIds) {
    if (id !== flow.start_block_id && !referencedIds.has(id)) {
      issues.push({ level: "warning", block_id: id, message: `bloco orfao` });
    }
  }
  for (const v of usedVars) {
    if (!declaredVars.has(v)) {
      issues.push({ level: "error", message: `variavel '${v}' usada antes de declarar` });
    }
  }
  if (!Object.values(flow.blocks).some((b) => b.type === "redirect")) {
    issues.push({ level: "warning", message: "nenhum bloco terminal 'redirect'" });
  }
  return issues;
}

export function parseAndValidate(json: unknown): {
  ok: boolean;
  flow?: Flow;
  issues: ValidationIssue[];
} {
  const parsed = Flow.safeParse(json);
  if (!parsed.success) {
    return {
      ok: false,
      issues: parsed.error.issues.map((i) => ({
        level: "error" as const,
        message: `${i.path.join(".")}: ${i.message}`,
      })),
    };
  }
  const structuralIssues = validateFlowStructure(parsed.data);
  const hasErrors = structuralIssues.some((i) => i.level === "error");
  return { ok: !hasErrors, flow: parsed.data, issues: structuralIssues };
}
