/**
 * NSKeyedArchiver Parser for Xcode xcuserstate files
 *
 * Parses binary plist files encoded with NSKeyedArchiver format
 * to extract ActiveScheme and ActiveRunDestination values.
 *
 * Uses bplist-parser for robust binary plist parsing instead of
 * relying on plutil output format which can change between macOS versions.
 */

import { readFileSync } from 'fs';
import { parseBuffer as bplistParseBuffer } from 'bplist-parser';

interface BplistUID {
  UID: number;
}

interface BplistResult {
  $archiver?: string;
  $objects?: unknown[];
  $top?: Record<string, unknown>;
  $version?: number;
}

/** Parsed xcuserstate result */
export interface XcodeStateResult {
  scheme?: string;
  simulatorId?: string;
  simulatorPlatform?: string;
  deviceLocation?: string;
}

/** Represents a dictionary in the NSKeyedArchiver format */
interface ArchivedDict {
  'NS.keys'?: BplistUID[];
  'NS.objects'?: BplistUID[];
  [key: string]: unknown;
}

/**
 * Checks if a value is a bplist UID reference
 */
function isUID(value: unknown): value is BplistUID {
  return (
    typeof value === 'object' &&
    value !== null &&
    'UID' in value &&
    typeof (value as BplistUID).UID === 'number'
  );
}

/**
 * Resolves a UID to its value in the $objects array
 */
function resolveUID(objects: unknown[], uid: BplistUID | unknown): unknown {
  if (!isUID(uid)) return uid;
  const index = uid.UID;
  if (index < 0 || index >= objects.length) return undefined;
  return objects[index];
}

/**
 * Finds the index of a string in the $objects array
 */
function findStringIndex(objects: unknown[], value: string): number {
  return objects.findIndex((obj) => obj === value);
}

/**
 * Finds a dictionary that has the given key index in its NS.keys array
 */
function findDictWithKey(objects: unknown[], keyIndex: number): ArchivedDict | undefined {
  for (const obj of objects) {
    if (typeof obj !== 'object' || obj === null) continue;
    const dict = obj as ArchivedDict;
    const keys = dict['NS.keys'];
    if (!Array.isArray(keys)) continue;

    const hasKey = keys.some((k) => isUID(k) && k.UID === keyIndex);
    if (hasKey) return dict;
  }
  return undefined;
}

/**
 * Gets the value for a key in an NS.keys/NS.objects dictionary
 */
function getValueForKey(objects: unknown[], dict: ArchivedDict, keyIndex: number): unknown {
  const keys = dict['NS.keys'];
  const values = dict['NS.objects'];

  if (!Array.isArray(keys) || !Array.isArray(values)) return undefined;

  const keyPosition = keys.findIndex((k) => isUID(k) && k.UID === keyIndex);
  if (keyPosition === -1 || keyPosition >= values.length) return undefined;

  return resolveUID(objects, values[keyPosition]);
}

/**
 * Main entry point: parses xcuserstate file and extracts Xcode state
 *
 * @param xcuserstatePath - Path to UserInterfaceState.xcuserstate file
 * @returns Extracted scheme and simulator information
 */
export function parseXcuserstate(xcuserstatePath: string): XcodeStateResult {
  const result: XcodeStateResult = {};

  try {
    const buffer = readFileSync(xcuserstatePath);
    const [root] = bplistParseBuffer(buffer) as [BplistResult];

    if (!root || root.$archiver !== 'NSKeyedArchiver' || !Array.isArray(root.$objects)) {
      return result;
    }

    const objects = root.$objects;

    // Find key indices
    const activeSchemeIdx = findStringIndex(objects, 'ActiveScheme');
    const activeRunDestIdx = findStringIndex(objects, 'ActiveRunDestination');
    const ideNameStringIdx = findStringIndex(objects, 'IDENameString');
    const targetDeviceLocationIdx = findStringIndex(objects, 'targetDeviceLocation');

    if (activeSchemeIdx === -1 && activeRunDestIdx === -1) {
      return result;
    }

    // Find the dictionary containing ActiveScheme key
    const parentDict = findDictWithKey(objects, activeSchemeIdx);
    if (!parentDict) {
      return result;
    }

    // Extract scheme name: ActiveScheme -> { IDENameString -> "SchemeName" }
    if (activeSchemeIdx !== -1 && ideNameStringIdx !== -1) {
      const schemeObj = getValueForKey(objects, parentDict, activeSchemeIdx);
      if (typeof schemeObj === 'object' && schemeObj !== null) {
        const schemeDict = schemeObj as ArchivedDict;
        const schemeName = getValueForKey(objects, schemeDict, ideNameStringIdx);
        if (typeof schemeName === 'string') {
          result.scheme = schemeName;
        }
      }
    }

    // Extract run destination: ActiveRunDestination -> { targetDeviceLocation -> "dvtdevice-..." }
    if (activeRunDestIdx !== -1 && targetDeviceLocationIdx !== -1) {
      const destObj = getValueForKey(objects, parentDict, activeRunDestIdx);
      if (typeof destObj === 'object' && destObj !== null) {
        const destDict = destObj as ArchivedDict;
        const location = getValueForKey(objects, destDict, targetDeviceLocationIdx);
        if (typeof location === 'string') {
          result.deviceLocation = location;

          // Extract UUID from location string: "dvtdevice-iphonesimulator:UUID"
          const match = location.match(/dvtdevice-([a-z]+):([A-F0-9-]{36})/i);
          if (match) {
            result.simulatorPlatform = match[1];
            result.simulatorId = match[2];
          }
        }
      }
    }
  } catch (error) {
    // Return empty result on error - this is best-effort parsing
    // that should never crash the server
    console.error('Failed to parse xcuserstate:', error);
  }

  return result;
}

/**
 * Parses xcuserstate from a Buffer (useful for testing with fixtures)
 *
 * @param buffer - Buffer containing the xcuserstate binary plist
 * @returns Extracted scheme and simulator information
 */
export function parseXcuserstateBuffer(buffer: Buffer): XcodeStateResult {
  const result: XcodeStateResult = {};

  try {
    const [root] = bplistParseBuffer(buffer) as [BplistResult];

    if (!root || root.$archiver !== 'NSKeyedArchiver' || !Array.isArray(root.$objects)) {
      return result;
    }

    const objects = root.$objects;

    const activeSchemeIdx = findStringIndex(objects, 'ActiveScheme');
    const activeRunDestIdx = findStringIndex(objects, 'ActiveRunDestination');
    const ideNameStringIdx = findStringIndex(objects, 'IDENameString');
    const targetDeviceLocationIdx = findStringIndex(objects, 'targetDeviceLocation');

    if (activeSchemeIdx === -1 && activeRunDestIdx === -1) {
      return result;
    }

    const parentDict = findDictWithKey(objects, activeSchemeIdx);
    if (!parentDict) {
      return result;
    }

    if (activeSchemeIdx !== -1 && ideNameStringIdx !== -1) {
      const schemeObj = getValueForKey(objects, parentDict, activeSchemeIdx);
      if (typeof schemeObj === 'object' && schemeObj !== null) {
        const schemeDict = schemeObj as ArchivedDict;
        const schemeName = getValueForKey(objects, schemeDict, ideNameStringIdx);
        if (typeof schemeName === 'string') {
          result.scheme = schemeName;
        }
      }
    }

    if (activeRunDestIdx !== -1 && targetDeviceLocationIdx !== -1) {
      const destObj = getValueForKey(objects, parentDict, activeRunDestIdx);
      if (typeof destObj === 'object' && destObj !== null) {
        const destDict = destObj as ArchivedDict;
        const location = getValueForKey(objects, destDict, targetDeviceLocationIdx);
        if (typeof location === 'string') {
          result.deviceLocation = location;

          const match = location.match(/dvtdevice-([a-z]+):([A-F0-9-]{36})/i);
          if (match) {
            result.simulatorPlatform = match[1];
            result.simulatorId = match[2];
          }
        }
      }
    }
  } catch (error) {
    console.error('Failed to parse xcuserstate buffer:', error);
  }

  return result;
}

// Export helpers for testing
export { isUID, resolveUID, findStringIndex, findDictWithKey, getValueForKey };
