// Convert proto-generated types to model types.
//
// This module handles the conversion between the protobuf wire format
// (generated by buf) and the common client model types (from WASM).
//
// Proto uses `node.case` discriminated unions, where:
// - `node.case === "leaf"` → WindowLeaf
// - `node.case === "split"` → WindowSplit
// - `node.case === undefined` → empty/null

import type {
  WindowNode,
  SplitDirection as ProtoSplitDirection,
} from "../gen/reovim/v2/state_pb.js";
import { SplitDirection as ProtoSplitDirectionEnum } from "../gen/reovim/v2/state_pb.js";
import type { LogicalLayout, SplitDirection } from "./bindings.js";

/**
 * Convert proto SplitDirection enum to model string literal.
 *
 * Proto enum values:
 * - UNSPECIFIED = 0
 * - HORIZONTAL = 1  → "Horizontal" (windows stacked vertically)
 * - VERTICAL = 2    → "Vertical" (windows side by side)
 */
function convertSplitDirection(proto: ProtoSplitDirection): SplitDirection {
  switch (proto) {
    case ProtoSplitDirectionEnum.HORIZONTAL:
      return "Horizontal";
    case ProtoSplitDirectionEnum.VERTICAL:
      return "Vertical";
    default:
      // UNSPECIFIED or unknown - default to vertical
      return "Vertical";
  }
}

/**
 * Convert proto WindowNode to LogicalLayout.
 *
 * Proto structure:
 * ```typescript
 * WindowNode.node: {
 *   case: "leaf";
 *   value: WindowLeaf;  // { windowId, bufferId, rect? }
 * } | {
 *   case: "split";
 *   value: WindowSplit;  // { direction, children }
 * } | {
 *   case: undefined;
 *   value?: undefined;
 * }
 * ```
 *
 * Model structure (Rust enum serialized by tsify):
 * ```typescript
 * LogicalLayout =
 *   | { Single: { buffer_id: number; viewport_id: number } }
 *   | { Split: { direction: SplitDirection; children: LogicalLayout[]; ratios: number[] } }
 *   | { Tabs: { tabs: LogicalLayout[]; active: number } }
 * ```
 *
 * @param node - Proto WindowNode from the server
 * @returns LogicalLayout for the WASM interpreter, or null if invalid
 */
export function protoToLogicalLayout(node: WindowNode): LogicalLayout | null {
  const payload = node.node;
  if (!payload || payload.case === undefined) {
    return null;
  }

  switch (payload.case) {
    case "leaf": {
      const leaf = payload.value;
      return {
        Single: {
          // Convert bigint to number for the model
          // Note: Window/buffer IDs are typically small enough for number
          buffer_id: Number(leaf.bufferId),
          viewport_id: Number(leaf.windowId),
        },
      };
    }

    case "split": {
      const split = payload.value;
      const direction = convertSplitDirection(split.direction);

      // Recursively convert children
      const children = split.children
        .map(protoToLogicalLayout)
        .filter((c): c is LogicalLayout => c !== null);

      if (children.length === 0) {
        return null;
      }

      // Proto doesn't include ratios, default to equal distribution
      const ratios = children.map(() => 1 / children.length);

      return {
        Split: { direction, children, ratios },
      };
    }

    default:
      return null;
  }
}

/**
 * Convert LayoutChangedPayload (flat window list) to LogicalLayout.
 *
 * Note: The notification uses a simplified WindowInfo[] format,
 * not the recursive tree structure. For full layout information,
 * the client should re-fetch using GetLayout RPC.
 *
 * This function creates a simple single-window layout for the focused window.
 * For multi-window layouts, use protoToLogicalLayout with GetLayout response.
 *
 * @param windows - Array of window info from notification
 * @param focusedWindowId - The currently focused window ID
 * @returns LogicalLayout for the focused window, or null if not found
 */
export function layoutNotificationToLogical(
  windows: Array<{ windowId: bigint; bufferId: bigint }>,
  focusedWindowId: bigint
): LogicalLayout | null {
  // Find the focused window
  const focused = windows.find((w) => w.windowId === focusedWindowId);
  if (!focused) {
    // Fallback to first window if focused not found
    const first = windows[0];
    if (!first) return null;
    return {
      Single: {
        buffer_id: Number(first.bufferId),
        viewport_id: Number(first.windowId),
      },
    };
  }

  return {
    Single: {
      buffer_id: Number(focused.bufferId),
      viewport_id: Number(focused.windowId),
    },
  };
}

/**
 * Check if a proto WindowNode is a leaf (single window).
 */
export function isProtoLeaf(node: WindowNode): boolean {
  return node.node.case === "leaf";
}

/**
 * Check if a proto WindowNode is a split (multiple windows).
 */
export function isProtoSplit(node: WindowNode): boolean {
  return node.node.case === "split";
}

/**
 * Get the buffer ID from a proto WindowNode if it's a leaf.
 */
export function getProtoBufferId(node: WindowNode): bigint | null {
  if (node.node.case === "leaf") {
    return node.node.value.bufferId;
  }
  return null;
}

/**
 * Get the window ID from a proto WindowNode if it's a leaf.
 */
export function getProtoWindowId(node: WindowNode): bigint | null {
  if (node.node.case === "leaf") {
    return node.node.value.windowId;
  }
  return null;
}

/**
 * Collect all window IDs from a proto WindowNode tree.
 */
export function getAllProtoWindowIds(node: WindowNode): bigint[] {
  const result: bigint[] = [];

  function collect(n: WindowNode) {
    if (n.node.case === "leaf") {
      result.push(n.node.value.windowId);
    } else if (n.node.case === "split") {
      for (const child of n.node.value.children) {
        collect(child);
      }
    }
  }

  collect(node);
  return result;
}

/**
 * Count windows in a proto WindowNode tree.
 */
export function countProtoWindows(node: WindowNode): number {
  if (node.node.case === "leaf") {
    return 1;
  } else if (node.node.case === "split") {
    return node.node.value.children.reduce(
      (sum, child) => sum + countProtoWindows(child),
      0
    );
  }
  return 0;
}
