/**
 * Types mirroring what PHP hands the client.
 *
 * These describe the wire format only. Node behaviour is never redefined here —
 * the catalogue is server-authored and this file just types the JSON it sends.
 */

export type PlanKey = 'free' | 'personal' | 'business' | 'agency';

export type NodeCategoryKey =
  | 'trigger'
  | 'action'
  | 'condition'
  | 'filter'
  | 'transform'
  | 'logic'
  | 'utility'
  | 'integration'
  | 'ai';

export interface PortDefinition {
  id: string;
  label: string;
  description: string;
  required: boolean;
}

export interface NodeCapabilities {
  retryable: boolean;
  idempotent: boolean;
  async: boolean;
  performsHttp: boolean;
  requiredFeature: string | null;
  timeoutSeconds: number;
}

/** A JSON Schema fragment as authored by a node definition in PHP. */
export interface ConfigSchema {
  type?: string;
  title?: string;
  description?: string;
  properties?: Record<string, ConfigSchema>;
  required?: string[];
  enum?: unknown[];
  default?: unknown;
  format?: string;
  minimum?: number;
  maximum?: number;
  minLength?: number;
  maxLength?: number;
  items?: ConfigSchema;
  /** Where the field's dropdown gets its choices. See app/Nodes/FieldOptions.php. */
  'x-options'?: string;
  /** A sibling field whose value narrows those choices, such as a term's taxonomy. */
  'x-options-parent'?: string;
  [key: string]: unknown;
}

/** One choice an options source offers. */
export interface FieldOption {
  value: string;
  label: string;
}

export interface NodeSummary {
  type: string;
  label: string;
  description: string;
  integration: string;
  /** Inline SVG authored by the node in PHP. */
  icon: string;
  outputSchema: ConfigSchema;
  category: NodeCategoryKey;
  categoryLabel: string;
  /** The same thing named for one node rather than a group: "Trigger". */
  categoryName: string;
  categoryOrder: number;
  isEntryPoint: boolean;
  inputs: PortDefinition[];
  outputs: PortDefinition[];
  configSchema: ConfigSchema;
  capabilities: NodeCapabilities;
  available: boolean;
  /** Whether the plan covers it, whatever else it needs. */
  licensed: boolean;
  /**
   * Why an unavailable node is unavailable: `plan` wants an upgrade, `plugin`
   * wants its integration's plugin installed. null when it is available.
   */
  lockReason: 'plan' | 'plugin' | null;
  requiredPlan: PlanKey | null;
}

/** One placed node in a saved workflow graph. */
export interface GraphNode {
  id: string;
  type: string;
  config: Record<string, unknown>;
  position: { x: number; y: number };
  label: string;
  disabled: boolean;
}

export interface GraphEdge {
  id: string;
  source: string;
  target: string;
  sourceHandle: string;
  targetHandle: string;
}

export interface WorkflowGraph {
  nodes: GraphNode[];
  edges: GraphEdge[];
  /** Sticky notes on the canvas. Never part of a run; absent when there are none. */
  notes?: StickyNote[];
}

export type NoteColor = 'yellow' | 'blue' | 'green' | 'pink' | 'purple' | 'gray';

export interface StickyNote {
  id: string;
  text: string;
  color: NoteColor;
  position: { x: number; y: number };
  width: number;
  height: number;
}

export interface ValidationIssue {
  field?: string;
  code: string;
  message: string;
}

export interface ValidationReport {
  valid: boolean;
  errors: ValidationIssue[];
  warnings: ValidationIssue[];
}

/** What a workflow is made of and how it has been doing, counted server-side. */
export interface WorkflowStats {
  triggers: number;
  actions: number;
  completedRuns: number;
  failedRuns: number;
  totalRuns: number;
  /** UTC datetime of the most recent run, or null if it has never run. */
  lastRunAt: string | null;
  /**
   * Integration ids the workflow connects, in the order a run reaches them.
   * Only the list endpoint sends it; Logic and Data tools are left out.
   */
  apps?: WorkflowApps;
}

export interface WorkflowApps {
  trigger: string[];
  steps: string[];
}

/** Name and logo of an integration a listed workflow uses. */
export interface AppBadgeInfo {
  name: string;
  /** Inline SVG authored by the integration in PHP. */
  icon: string;
  /** False when the plugin it connects is not active on this site. */
  available: boolean;
}

export interface WorkflowSummary {
  id: number;
  name: string;
  slug: string;
  description: string;
  status: 'draft' | 'active' | 'inactive';
  isActive: boolean;
  createdAt: string;
  updatedAt: string;
  stats: WorkflowStats;
}

export interface Workflow extends WorkflowSummary {
  graph: WorkflowGraph;
  latestVersionId: number;
  activeVersionId: number;
}

/** One formatter a token pipe can use: `{{ path | name: "arg" }}`. */
export interface FormatterSummary {
  name: string;
  label: string;
  group: string;
  /** Names of its arguments, in order; empty when it takes none. */
  arguments: string[];
  /** True when the site's plan does not include it. */
  locked: boolean;
}

/** An export: the file's name and content, and what was left out of it. */
export interface ExportResult {
  fileName: string;
  document: unknown;
  /** Secrets and site-only values that were not written to the file. */
  removed: string[];
}

export interface ImportProblem {
  level: 'error' | 'warning' | 'info';
  message: string;
}

/** One workflow in an import preview. */
export interface ImportPreviewItem {
  /** Its position in the file. */
  index: number;
  name: string;
  description: string;
  steps: number;
  triggers: string[];
  problems: ImportProblem[];
  importable: boolean;
}

export interface ImportPreview {
  workflows: ImportPreviewItem[];
}

export interface ImportResult {
  created: Array<{ id: number; name: string }>;
  skipped: Array<{ name: string; reason: string }>;
}

/** What a config field knows about the workflow and node it belongs to. */
export interface FieldContext {
  workflowId: number;
  /** Whether the workflow is active, so its active version is what runs. */
  active: boolean;
  /** Unsaved changes on the canvas. */
  dirty: boolean;
  nodeId: string;
}

export interface ImportColumn {
  key: string;
  label: string;
}

/** A file import trigger's settings. See app/Integrations/Core/FileImportTrigger__premium_only.php. */
export interface ImportConfig {
  id: number;
  fileName: string;
  format: string;
  totalRows: number;
  columns: ImportColumn[];
  recordPath: string;
}

export type ImportStatus = 'ready' | 'running' | 'done' | 'cancelled' | 'stopped' | 'failed' | 'replaced' | 'expired';

/** An uploaded file and how far its import has got. See app/Imports/ImportService__premium_only.php. */
export interface ImportSummary {
  id: number;
  workflowId: number;
  nodeId: string;
  status: ImportStatus;
  fileName: string;
  format: string;
  columns: ImportColumn[];
  totalRows: number;
  /** Rows handed to a run so far. */
  queuedRows: number;
  /** Rows skipped because their key was imported before, unchanged. */
  skippedRows: number;
  keyColumn: string;
  error: string | null;
  createdAt: string;
  updatedAt: string;
  runs: Record<'pending' | 'running' | 'waiting' | 'success' | 'failed' | 'halted' | 'aborted', number>;
  failedRuns: number;
  /** Nothing more will be handed to runs, and no run is still going. */
  finished: boolean;
}

export interface FailedImportRow {
  rowNumber: number;
  executionId: number;
  status: string;
  error: string;
  row: Record<string, unknown>;
}

export interface ImportDetail extends ImportSummary {
  /** The first failed rows, with why. */
  failedRows: FailedImportRow[];
}

/** One step of a template: a node, as the library names it. */
export interface TemplateStep {
  id: string;
  label: string;
  /** Singular: "Trigger", "Action", "Condition". */
  category: string;
  /** The integration it belongs to, e.g. "WooCommerce". */
  integration: string;
  /** Inline SVG authored in PHP. */
  icon: string;
  /** The lowest plan that includes this step. */
  plan: PlanKey;
}

/** A ready-made workflow in the template gallery. See app/Templates/TemplateCatalogue.php. */
export interface TemplateSummary {
  id: string;
  name: string;
  description: string;
  category: string;
  /** One of the Free plan's templates. */
  free: boolean;
  /** The plan it belongs to, whatever this site's plan is. */
  plan: PlanKey;
  nodeCount: number;
  /** Its steps in order, for the details view. */
  steps: TemplateStep[];
  /** The systems it connects, for the card's icons. */
  integrations: Array<{ id: string; name: string; icon: string }>;
  /** What the author chooses after starting from it: "the course". */
  setup: string[];
  /** `plan` wants an upgrade and cannot be used; `plugin` can be used but needs a plugin to run. */
  lockReason: 'plan' | 'plugin' | null;
  requiredPlan: PlanKey | null;
  /** Names of the plugins it still needs. */
  needs: string[];
}

/** One comparison a condition rule can make. */
export interface ConditionOperatorSummary {
  name: string;
  /** Reads after the value: "is equal to". */
  label: string;
  /** 1 for a test of one value ("is empty"), 2 for a comparison with something. */
  arity: 1 | 2;
  /** True when the site's plan does not include it. */
  locked: boolean;
}

export interface ConditionRule {
  left: string;
  operator: string;
  right: string;
}

export interface ConditionGroup {
  match: 'all' | 'any';
  rules: ConditionRule[];
}

/** Rule groups combined with AND / OR. See app/Logic/Conditions.php. */
export interface ConditionSpec {
  match: 'all' | 'any';
  groups: ConditionGroup[];
}

/** One route of a Switch node. */
export interface SwitchRoute {
  name: string;
  conditions: ConditionSpec;
}

export interface BootstrapData {
  apiRoot: string;
  nonce: string;
  adminUrl: string;
  pluginUrl: string;
  version: string;
  locale: string;
  /** Who is looking at it, for a greeting. Never their email. */
  user: { firstName: string; displayName: string };
  plan: {
    key: PlanKey;
    label: string;
    isFree: boolean;
    isTrial: boolean;
    upgradeUrl: string | null;
  };
  lockedFeatures: string[];
  /** Whether any kind of connection can be saved. None can in the WordPress.org build. */
  hasConnections: boolean;
  /** flowwp.net pages the screens link to, as plain addresses. Built by SiteLinks in PHP; never assembled here. */
  docs: {
    workflowEditor: string;
    connections: string;
    templates: string;
    runHistory: string;
    plansAndLimits: string;
    upgradeToPremium: string;
    licenseActivation: string;
  };
  /** The formatter library, for the token picker's "Format" list. */
  formatters: FormatterSummary[];
  /** The comparisons, for the condition builder. */
  conditionOperators: ConditionOperatorSummary[];
  capabilities: {
    manageWorkflows: boolean;
    editWorkflows: boolean;
    viewExecutions: boolean;
    manageCredentials: boolean;
    manageSettings: boolean;
  };
}

/** Success envelope: { data, meta? } */
export interface ApiSuccess<T> {
  data: T;
  meta?: Record<string, unknown>;
}

/** Error envelope: { code, message, data: { status, errors } } */
export interface ApiError {
  code: string;
  message: string;
  data?: {
    status?: number;
    errors?: ValidationIssue[];
  };
}

declare global {
  interface Window {
    flowwpData?: BootstrapData;
  }
}

export interface IntegrationSummary {
  id: string;
  name: string;
  /** Inline SVG authored by the integration in PHP. */
  icon: string;
  available: boolean;
  requirement: string;
  nodeCount: number;
  /**
   * Where the node library files it: a WordPress plugin, an outside app
   * reached over its API, or one of FlowWP's own tools.
   */
  kind: IntegrationKind;
}

export type IntegrationKind = 'plugin' | 'app' | 'tool';

export interface DataContextLeaf {
  path: string;
  label: string;
  type: string;
  /** A short preview of the real value from the last run, when there was one. */
  sample: string | null;
}

export interface DataContextGroup {
  id: string;
  label: string;
  path: string;
  /** True when the node cannot describe its output, so only a test reveals it. */
  isUnknown: boolean;
  leaves: DataContextLeaf[];
}

export interface TestStep {
  nodeId: string;
  nodeType: string;
  status: string;
  attempt: number;
  /** The node's configuration as it resolved on this run, secrets masked. */
  input: Record<string, unknown>;
  output: Record<string, unknown>;
  ports: string[];
  errorCode: string | null;
  errorMessage: string | null;
  durationMs: number;
  /** The name on the canvas: the author's label, or the node type's. */
  label: string;
  /** Inline SVG for the node type. */
  icon: string;
  /** The node's category, e.g. "Trigger" or "Action". */
  typeLabel: string;
  /** The step's outcome in plain words. */
  statusLabel: string;
  /** Why a step that did not run was passed over; empty when self-evident. */
  explanation: string;
  /** Named branches taken, for nodes with more than one output. */
  branches: string[];
}

export interface TestRunResult {
  executionId: number;
  status: string;
  statusLabel: string;
  /** One sentence about how the run went. */
  summary: string;
  steps: TestStep[];
}

/** One row of the run history. */
export interface ExecutionSummary {
  id: number;
  workflowId: number;
  workflowName: string;
  versionId: number;
  status: string;
  statusLabel: string;
  /** False while a run is still pending, running or waiting. */
  isTerminal: boolean;
  source: string;
  /** What started it, in words. */
  sourceLabel: string;
  triggerKey: string;
  errorCode: string | null;
  errorMessage: string | null;
  startedAt: string | null;
  finishedAt: string | null;
  durationMs: number;
  createdAt: string;
}

export interface ExecutionDetail extends ExecutionSummary {
  /** The same one-sentence summary the test-run panel shows. */
  summary: string;
  steps: TestStep[];
  trigger: Record<string, unknown>;
}

/** How long this site keeps history. `days` null means forever. */
export interface RetentionInfo {
  days: number | null;
  expired: number;
}

/** A kind of connection, such as an Anthropic API key, and what it asks for. */
export interface CredentialType {
  id: string;
  label: string;
  description: string;
  fields: Record<string, { label: string; secret: boolean; required: boolean; help?: string }>;
}

/**
 * A saved connection. Secrets never come back from the server: `hint` is the
 * last characters of the key, and `values` holds only the non-secret fields.
 */
export interface Connection {
  id: number;
  type: string;
  label: string;
  hint: string;
  /** False when the site's keys changed since it was saved and it must be entered again. */
  readable: boolean;
  values: Record<string, string>;
  createdAt: string;
  updatedAt: string;
}
