import type {
  JsonValue,
  PluginNetworkResponseDto,
  PluginPropertyMutationResult,
  PluginSearchRowDto,
} from "@thortiq/plugin-api";
import {
  SEARCH_PAGE_SIZE,
  SEARCH_QUERY,
  buildBoundedPreview,
  collectJiraNotes,
  normalizePropertyMappings,
  planJiraProjections,
  summarizeApplyResults,
  toPropertyBatchOperations,
  type ApplySummary,
  type JiraIssueProjection,
  type JiraProjectionPlan,
} from "./workflow-core.js";
import {
  parseJiraIssuePageResponse,
} from "./jira-client.js";

interface LiveAction {
  readonly action: string;
  readonly values: Readonly<Record<string, JsonValue>>;
}

interface LiveViewApi {
  request(method: string, params?: unknown): Promise<unknown>;
  readonly ui: {
    setView(view: unknown): Promise<void>;
    updateView(view: unknown): Promise<void>;
    onAction(callback: (action: LiveAction) => void | Promise<void>): {
      dispose(): void;
    };
  };
}

interface LiveMountEvent {
  readonly instanceId: string;
}

interface LiveGuestApi {
  readonly lifecycle: {
    onMount(callback: (event: LiveMountEvent) => (
      void | { dispose(): void } | Promise<void | { dispose(): void }>
    )): {
      dispose(): void;
    };
  };
  forLifecycle(instanceId: string): LiveViewApi;
}

interface JiraLiveState {
  rows: readonly PluginSearchRowDto[];
  issues: ReadonlyMap<string, JiraIssueProjection>;
  plans: readonly JiraProjectionPlan[];
  preview: { readonly message: string; readonly items: readonly string[] };
  confirmed: boolean;
  summary: ApplySummary | null;
}

function createState(): JiraLiveState {
  return {
    rows: Object.freeze([]),
    issues: new Map(),
    plans: Object.freeze([]),
    preview: Object.freeze({ message: "No preview yet.", items: Object.freeze([]) }),
    confirmed: false,
    summary: null,
  };
}

function mappings() {
  const normalized = normalizePropertyMappings({
    status: "JiraStatus",
    assignee: "JiraAssignee",
    lastSynced: "JiraLastSynced",
  });
  if (!normalized.mappings) {
    throw new Error("The fixed Jira example mappings are invalid.");
  }
  return normalized.mappings;
}

function jiraRequest(keys: readonly string[]) {
  return {
    connection: "atlassian",
    path: "/rest/api/3/search/jql",
    method: "GET",
    query: {
      jql: `key in (${keys.join(", ")})`,
      fields: ["key", "status", "assignee", "updated"],
      maxResults: "100",
    },
    headers: { Accept: "application/json" },
    responseType: "json",
    timeoutMs: 30_000,
    credential: {
      type: "basic",
      usernameSetting: "email",
      secret: "apiToken",
    },
  };
}

/**
 * Each action crosses the QuickJS broker at most once. The runtime-neutral
 * integration owns matching, provider parsing, projection, preview, and
 * conditional batch construction; this adapter only sequences public runtime
 * capabilities around those canonical functions.
 */
export async function runJiraAction(
  view: LiveViewApi,
  state: JiraLiveState,
  action: LiveAction,
): Promise<void> {
  if (action.action === "load-jira-notes") {
    const result = await view.request("search.query", {
      query: SEARCH_QUERY,
      options: {
        includeBare: false,
        includeAssets: false,
        limit: SEARCH_PAGE_SIZE,
      },
    }) as { readonly rows: readonly PluginSearchRowDto[] };
    state.rows = Object.freeze([...result.rows]);
    state.confirmed = false;
    return;
  }

  const collected = collectJiraNotes(state.rows);
  if (action.action === "load-jira-issues") {
    if (collected.keys.length === 0) {
      state.issues = new Map();
      return;
    }
    const response = await view.request(
      "network.request",
      jiraRequest(collected.keys),
    ) as PluginNetworkResponseDto;
    if (response.status < 200 || response.status >= 300) {
      throw new Error("The synthetic Jira request failed.");
    }
    const parsed = parseJiraIssuePageResponse(response.body);
    if (!parsed) throw new Error("The Jira response did not match the public DTO.");
    const requested = new Set(collected.keys);
    state.issues = new Map(
      parsed.filter(issue => requested.has(issue.key)).map(issue => [issue.key, issue]),
    );
    state.confirmed = false;
    return;
  }

  if (action.action === "prepare-jira-preview") {
    state.plans = planJiraProjections(collected.notes, state.issues, mappings());
    const missing = collected.keys.filter(key => !state.issues.has(key));
    state.preview = buildBoundedPreview(
      state.plans,
      collected.duplicateKeys,
      collected.invalid.length,
      missing,
      0,
    );
    state.confirmed = false;
    return;
  }

  if (action.action === "confirm-jira-preview") {
    state.confirmed = await view.request("dialogs.confirm", {
      title: "Apply Jira property preview?",
      message: state.preview.message,
      confirmLabel: "Apply updates",
      cancelLabel: "Keep preview only",
    }) as boolean;
    return;
  }

  if (action.action === "apply-jira-preview") {
    if (!state.confirmed || state.plans.length === 0) return;
    const operations = toPropertyBatchOperations(state.plans);
    const results = await view.request("notes.updatePropertiesBatch", {
      operations,
      options: { operation: "jira-refresh" },
    }) as readonly PluginPropertyMutationResult[];
    state.summary = summarizeApplyResults(results);
    state.confirmed = false;
  }
}

export function createJiraActionHandler(view: LiveViewApi) {
  const state = createState();
  return (action: LiveAction) => runJiraAction(view, state, action);
}

function viewDocument() {
  return {
    version: 1,
    root: {
      id: "jira-root",
      type: "stack",
      gap: "md",
      children: [
        { id: "title", type: "heading", level: 2, text: "Jira property sync" },
        {
          id: "instructions",
          type: "text",
          text: "Load notes, load issues, prepare and confirm the preview, then apply it.",
        },
        {
          id: "notes",
          type: "search",
          query: SEARCH_QUERY,
          columns: ["title", "JiraNumber", "JiraStatus"],
          presentation: "table",
          pageSize: SEARCH_PAGE_SIZE,
          includeBare: false,
          includeAssets: false,
        },
        {
          id: "load-notes",
          type: "button",
          label: "1. Load Jira notes",
          action: "load-jira-notes",
        },
        {
          id: "load-issues",
          type: "button",
          label: "2. Load Jira issues",
          action: "load-jira-issues",
        },
        {
          id: "prepare",
          type: "button",
          label: "3. Prepare preview",
          action: "prepare-jira-preview",
        },
        {
          id: "confirm",
          type: "button",
          label: "4. Confirm preview",
          action: "confirm-jira-preview",
        },
        {
          id: "apply",
          type: "button",
          label: "5. Apply confirmed preview",
          action: "apply-jira-preview",
        },
      ],
    },
  };
}

async function mount(
  forLifecycle: LiveGuestApi["forLifecycle"],
  event: LiveMountEvent,
) {
  const view = forLifecycle(event.instanceId);
  await view.ui.setView(viewDocument());
  return view.ui.onAction(createJiraActionHandler(view));
}

export function activate(api: LiveGuestApi): void {
  const forLifecycle = api.forLifecycle;
  api.lifecycle.onMount(event => mount(forLifecycle, event));
}

export default activate;
