const STORAGE_KEY = 'cbm-layout-v1';

export const WORKSPACE_IDS = ['revision', 'organization', 'time', 'administration'];

export const WORKSPACE_PANELS = {
  revision: ['repo', 'history', 'devices', 'console'],
  organization: ['availability', 'members', 'devices', 'history', 'console'],
  time: [],
  administration: ['server', 'users', 'devices', 'console'],
};

/**
 * Tiling window manager layout engine.
 *
 * Rules:
 * - Layout is a binary split tree; each panel appears at most once.
 * - Dropping on an edge splits THAT panel's footprint 50/50 with the dragged panel.
 * - Moving a panel removes it from its old slot first (never copies).
 * - Closing a panel removes its leaf and collapses single-child splits (siblings expand).
 * - Opening a panel inserts it at the bottom of the largest visible panel.
 *
 * Example progression:
 *   [L/R]  →  H[repo, history]
 *   [L/R + console]  →  H[ repo, V[history, console] ]
 *   [[L/R]/B]  →  V[ H[history, repo], console ]
 */

export const ALL_PANELS = [
  ...new Set([
    ...WORKSPACE_PANELS.revision,
    ...WORKSPACE_PANELS.organization,
    ...WORKSPACE_PANELS.administration,
  ]),
];

export function createInitialLayout(workspace = 'revision') {
  if (workspace === 'organization') {
    return {
      type: 'split',
      direction: 'horizontal',
      ratio: 0.86,
      first: { type: 'leaf', panelId: 'availability' },
      second: { type: 'leaf', panelId: 'members' },
    };
  }
  if (workspace === 'administration') {
    // Top row: Server Management (left) beside a stacked User/Device column
    // (right). Console spans the bottom.
    return {
      type: 'split',
      direction: 'vertical',
      ratio: 0.8,
      first: {
        type: 'split',
        direction: 'horizontal',
        ratio: 0.58,
        first: { type: 'leaf', panelId: 'server' },
        second: {
          type: 'split',
          direction: 'vertical',
          ratio: 0.5,
          first: { type: 'leaf', panelId: 'users' },
          second: { type: 'leaf', panelId: 'devices' },
        },
      },
      second: { type: 'leaf', panelId: 'console' },
    };
  }
  if (workspace === 'time') return null;

  return {
    type: 'split',
    direction: 'horizontal',
    ratio: 0.75,
    first: { type: 'leaf', panelId: 'repo' },
    second: { type: 'leaf', panelId: 'history' },
  };
}

export function deepClone(node) {
  return JSON.parse(JSON.stringify(node));
}

export function findLeaf(node, panelId, parent = null, childKey = null) {
  if (node.type === 'leaf') {
    return node.panelId === panelId ? { node, parent, childKey } : null;
  }
  return (
    findLeaf(node.first, panelId, node, 'first') ||
    findLeaf(node.second, panelId, node, 'second')
  );
}

function replaceChild(tree, parent, key, child) {
  if (!parent) return child;

  function walk(node) {
    if (node === parent) return { ...node, [key]: child };
    if (node.type === 'split') {
      return { ...node, first: walk(node.first), second: walk(node.second) };
    }
    return node;
  }

  return walk(tree);
}

/** Remove a panel from the layout; collapse single-child splits. */
export function removePanel(layout, panelId) {
  function remove(node) {
    if (node.type === 'leaf') {
      return node.panelId === panelId ? null : node;
    }
    const first = remove(node.first);
    const second = remove(node.second);
    if (!first) return second;
    if (!second) return first;
    return { ...node, first, second };
  }

  return remove(deepClone(layout));
}

/**
 * Split the target panel's region and place `panelId` on the given edge.
 * The dragged panel is detached from its old location first (move, not copy).
 */
export function insertAtEdge(layout, targetPanelId, panelId, edge) {
  if (targetPanelId === panelId) return layout;

  let tree = removePanel(layout, panelId);
  if (!tree) return layout;

  const target = findLeaf(tree, targetPanelId);
  if (!target) return layout;

  const newLeaf = { type: 'leaf', panelId };
  const isHorizontal = edge === 'left' || edge === 'right';
  const direction = isHorizontal ? 'horizontal' : 'vertical';

  const newSplit =
    edge === 'left' || edge === 'top'
      ? { type: 'split', direction, ratio: 0.5, first: newLeaf, second: target.node }
      : { type: 'split', direction, ratio: 0.5, first: target.node, second: newLeaf };

  if (!target.parent) return newSplit;

  return replaceChild(tree, target.parent, target.childKey, newSplit);
}

/** Relative footprint area (0–1) for each leaf. */
export function leafAreas(layout) {
  const areas = new Map();

  function walk(node, area) {
    if (node.type === 'leaf') {
      areas.set(node.panelId, area);
      return;
    }
    walk(node.first, area * node.ratio);
    walk(node.second, area * (1 - node.ratio));
  }

  walk(layout, 1);
  return areas;
}

/**
 * Largest leaf by area; ties prefer the bottom-right region (more "second" branches).
 */
export function findLargestPanelId(layout) {
  let bestId = null;
  let bestArea = -1;
  let bestScore = -1;

  function walk(node, area, depth, secondCount) {
    if (node.type === 'leaf') {
      const score = secondCount * 1000 + depth;
      if (area > bestArea || (area === bestArea && score > bestScore)) {
        bestArea = area;
        bestScore = score;
        bestId = node.panelId;
      }
      return;
    }
    walk(node.first, area * node.ratio, depth + 1, secondCount);
    walk(node.second, area * (1 - node.ratio), depth + 1, secondCount + 1);
  }

  walk(layout, 1, 0, 0);
  return bestId;
}

/** Open a panel in the bottom region of the largest visible window. */
export function insertDefault(layout, panelId) {
  let tree = removePanel(layout, panelId);
  if (!tree) tree = { type: 'leaf', panelId: 'repo' };

  const targetId = findLargestPanelId(tree);
  if (!targetId) return { type: 'leaf', panelId };

  return insertAtEdge(tree, targetId, panelId, 'bottom');
}

export function getVisiblePanels(layout, workspace = 'revision') {
  const panelIds = WORKSPACE_PANELS[workspace] ?? [];
  const vis = Object.fromEntries(panelIds.map((id) => [id, false]));
  if (!layout) return vis;

  function walk(node) {
    if (node.type === 'leaf') {
      if (node.panelId in vis) vis[node.panelId] = true;
      return;
    }
    walk(node.first);
    walk(node.second);
  }

  walk(layout);
  return vis;
}

export function setPanelVisible(layout, panelId, visible, workspace = 'revision') {
  if (!layout) return createInitialLayout(workspace);

  const isVisible = Boolean(findLeaf(layout, panelId));

  if (visible) {
    if (isVisible) return layout;
    return insertDefault(layout, panelId);
  }

  if (!isVisible) return layout;

  const vis = getVisiblePanels(layout, workspace);
  const count = Object.values(vis).filter(Boolean).length;
  if (count <= 1) return layout;

  const next = removePanel(layout, panelId);
  return next ?? layout;
}

export function updateSplitRatio(layout, path, ratio) {
  const next = deepClone(layout);
  let node = next;
  for (const key of path) {
    if (!node || node.type === 'leaf') return layout;
    node = node[key];
  }
  if (!node || node.type !== 'split') return layout;
  node.ratio = ratio;
  return next;
}

export function resetLayout(workspace = 'revision') {
  return createInitialLayout(workspace);
}

export function loadWorkspaceLayouts() {
  try {
    const saved = localStorage.getItem(STORAGE_KEY);
    if (saved) {
      const parsed = JSON.parse(saved);
      if (parsed && typeof parsed === 'object' && !parsed.type) return parsed;
    }
  } catch {
    /* use defaults */
  }
  return {
    revision: createInitialLayout('revision'),
    organization: createInitialLayout('organization'),
  };
}

export function saveWorkspaceLayouts(layouts) {
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(layouts));
  } catch {
    /* ignore */
  }
}

export function validateLayout(layout, workspace = 'revision') {
  const allowed = new Set(WORKSPACE_PANELS[workspace] ?? []);
  if (!layout) return createInitialLayout(workspace);

  const seen = new Set();

  function dedupe(node) {
    if (!node) return null;
    if (node.type === 'leaf') {
      if (!allowed.has(node.panelId) || seen.has(node.panelId)) return null;
      seen.add(node.panelId);
      return node;
    }
    const first = dedupe(node.first);
    const second = dedupe(node.second);
    if (!first && !second) return null;
    if (!first) return second;
    if (!second) return first;
    return { ...node, first, second };
  }

  const cleaned = dedupe(deepClone(layout));
  return cleaned ?? createInitialLayout(workspace);
}
