import assert from "node:assert/strict";
import test from "node:test";

class FakeClassList {
  constructor() {
    this.values = new Set();
  }

  toggle(name, enabled) {
    if (enabled) {
      this.values.add(name);
    } else {
      this.values.delete(name);
    }
  }
}

class FakeElement {
  constructor(tagName) {
    this.tagName = tagName;
    this.children = [];
    this.listeners = {};
    this.classList = new FakeClassList();
    this.className = "";
    this.textContent = "";
    this.value = "";
    this.disabled = false;
  }

  setAttribute(name, value) {
    this[name] = value;
  }

  append(...children) {
    this.children.push(...children);
  }

  addEventListener(type, listener) {
    this.listeners[type] = listener;
  }
}

function installDom() {
  const styles = new Map();
  globalThis.document = {
    head: {
      append(element) {
        if (element.id) styles.set(element.id, element);
      },
    },
    getElementById(id) {
      return styles.get(id) ?? null;
    },
    createElement(tagName) {
      return new FakeElement(tagName);
    },
  };
  globalThis.window = {
    confirm: () => true,
    prompt: () => "",
  };
  globalThis.setTimeout = () => 0;
}

installDom();

const {statusControl} = await import("../src/components/status-control.js");

function okResponse(overrides = {}) {
  return {
    target_type: "task",
    target_id: "00000000-0000-0000-0000-000000000001",
    actor: "dashboard",
    current_status: "todo",
    target_status: "progress",
    file_backed: true,
    archived_file: false,
    task_linked: true,
    reconciliation_class: "write_through",
    reason: "task status can be represented in the workplan task block",
    follow_up: "patch task block status and sync the DB from file",
    write_through_result: "applied",
    workplan_path: "workplans/STATE-WP-9999-demo.md",
    reconciliation_record_id: null,
    conflict: false,
    ...overrides,
  };
}

test("status control posts dashboard changes through reconciliation", async () => {
  const requests = [];
  globalThis.fetch = async (url, options) => {
    requests.push({url, options, body: JSON.parse(options.body)});
    return {
      ok: true,
      json: async () => okResponse(),
    };
  };

  const entity = {id: "00000000-0000-0000-0000-000000000001", status: "todo"};
  let saved = null;
  const root = statusControl({
    entity,
    type: "task",
    statuses: ["todo", "progress"],
    onSaved: (updated, result) => {
      saved = {updated, result};
    },
  });
  const [select, message] = root.children;

  select.value = "progress";
  await select.listeners.change();

  assert.equal(requests.length, 1);
  assert.equal(requests[0].url, "http://127.0.0.1:8000/reconciliation/state-change");
  assert.equal(requests[0].body.target_type, "task");
  assert.equal(requests[0].body.target_status, "progress");
  assert.equal(requests[0].body.expected_current_status, "todo");
  assert.equal(requests[0].body.apply, true);
  assert.equal(entity.status, "progress");
  assert.equal(message.textContent, "synced");
  assert.equal(saved.result.write_through_result, "applied");
});

test("status control keeps local state on reconciliation conflicts", async () => {
  const requests = [];
  globalThis.fetch = async (url, options) => {
    requests.push({url, options, body: JSON.parse(options.body)});
    return {
      ok: true,
      json: async () => okResponse({
        current_status: "done",
        target_status: "progress",
        reconciliation_class: "deferred",
        reason: "cached task status changed from expected 'todo' to 'done'",
        follow_up: "refresh the dashboard and retry the state change if it is still intended",
        write_through_result: "not_applicable",
        reconciliation_record_id: "00000000-0000-0000-0000-000000000002",
        conflict: true,
      }),
    };
  };

  const entity = {id: "00000000-0000-0000-0000-000000000001", status: "todo"};
  let saved = null;
  const root = statusControl({
    entity,
    type: "task",
    statuses: ["todo", "progress"],
    onSaved: () => {
      saved = true;
    },
  });
  const [select, message] = root.children;

  select.value = "progress";
  await select.listeners.change();

  assert.equal(requests.length, 1);
  assert.equal(entity.status, "todo");
  assert.equal(select.value, "todo");
  assert.equal(message.textContent, "out of sync");
  assert.equal(saved, null);
});
