export type Plane = 'accepts' | 'emits';

export interface MatrixUri {
  scheme: 'matrix';
  raw: string;

  /** Optional principal (user identity before @). */
  principal?: string;

  /** Optional root. If absent, this is a local-only semantic address. */
  root?: string;
  /** @deprecated Use root instead */
  realm?: string;

  /** Mount segments (slash-delimited). */
  mountSegments: string[];

  /** Component path (dot-delimited). */
  componentPath: string;

  plane: Plane;
  messageName: string;

  /** Derived canonical path: <mount...>/<componentPath>.<plane>.<messageName> */
  toPath: string;

  /** Query parameters (not routing) */
  query: Record<string, string>;
}

function parseQueryString(qs: string): Record<string, string> {
  const out: Record<string, string> = {};
  if (!qs) return out;
  for (const pair of qs.split('&')) {
    const eqIdx = pair.indexOf('=');
    if (eqIdx > 0) {
      out[decodeURIComponent(pair.slice(0, eqIdx))] = decodeURIComponent(pair.slice(eqIdx + 1));
    } else if (pair) {
      out[decodeURIComponent(pair)] = '';
    }
  }
  return out;
}

function canonicalToPath(mountSegments: string[], componentPath: string, plane: Plane, messageName: string): string {
  const atom = `${componentPath}.${plane}.${messageName}`;
  if (mountSegments.length === 0) return atom;
  return `${mountSegments.join('/')}/${atom}`;
}

/**
 * Parse a Matrix semantic URI.
 *
 * Accepts bare URIs (no scheme prefix):
 *
 * - Compact form:
 *   <root>/<mount...>/<componentPath>.<plane>.<messageName>
 *   <componentPath>.<plane>.<messageName>  (no root; local)
 *   <principal>@<root>/<mount...>/<componentPath>.<plane>.<messageName>
 *
 * - Segmented MXURI/1:
 *   <root>/<mount...>/-/<component...>/<plane>/<message>
 *
 * Tab roots (leaf roots) are normalized:
 *   <homeRoot>/~tab/<tabId>/... => root = <homeRoot>.tab.<tabId>
 *
 * Query parameters:
 *   <root>/path.accepts.cmd?key=value
 *
 * Rejects URIs with non-matrix schemes (http://, ftp://, etc.).
 */
export function parseMatrixUri(uri: string): MatrixUri | null {
  if (!uri || typeof uri !== 'string') return null;

  // Reject other schemes
  if (uri.includes('://') && !uri.startsWith('matrix://')) return null;

  // Strip optional matrix:// prefix (backward compat)
  let body = uri.startsWith('matrix://') ? uri.slice('matrix://'.length) : uri;

  // Split query string
  let queryStr = '';
  const qIdx = body.indexOf('?');
  if (qIdx >= 0) {
    queryStr = body.slice(qIdx + 1);
    body = body.slice(0, qIdx);
  }
  const query = parseQueryString(queryStr);

  // Split into slash-delimited segments
  const allSegs = body.split('/').filter(s => s.length > 0);

  // Segmented MXURI/1 if there is an explicit '-' segment (the /-/ delimiter).
  const delimIdx = allSegs.indexOf('-');

  if (delimIdx !== -1 && delimIdx > 0) {
    // Segmented form: root/mount.../-/component.../plane/message
    // First segment is root (possibly with principal@)
    const firstSeg = allSegs[0];
    let root: string;
    let principal: string | undefined;

    const atIdx = firstSeg.indexOf('@');
    if (atIdx > 0) {
      principal = firstSeg.slice(0, atIdx);
      root = firstSeg.slice(atIdx + 1);
    } else {
      root = firstSeg;
    }

    // Remaining segments before delimiter are mount + possible ~tab
    let preDash = allSegs.slice(1, delimIdx);

    // Tab normalization: root/~tab/<id>/...
    if (preDash.length >= 2 && preDash[0] === '~tab') {
      const tabId = preDash[1];
      root = `${root}.tab.${tabId}`;
      preDash = preDash.slice(2);
    }

    const mountSegments = preDash;
    const rest = allSegs.slice(delimIdx + 1);
    if (rest.length < 3) return null;

    const plane = rest[rest.length - 2];
    const messageName = rest[rest.length - 1];
    if (plane !== 'accepts' && plane !== 'emits') return null;

    const componentSegments = rest.slice(0, -2);
    if (componentSegments.length === 0) return null;

    const componentPath = componentSegments.join('.');
    const toPath = canonicalToPath(mountSegments, componentPath, plane, messageName);

    return {
      scheme: 'matrix',
      raw: uri,
      principal,
      root,
      realm: root,
      mountSegments,
      componentPath,
      plane,
      messageName,
      toPath,
      query,
    };
  }

  // Compact form parsing.
  // Find trailing .accepts.<msg> or .emits.<msg>
  const acceptsMatch = body.match(/^(.*)\.accepts\.([^/]+)$/);
  const emitsMatch = body.match(/^(.*)\.emits\.([^/]+)$/);
  const planeMatch = acceptsMatch || emitsMatch;
  if (!planeMatch) return null;

  const beforePlane = planeMatch[1];
  const messageName = planeMatch[2];
  const plane: Plane = acceptsMatch ? 'accepts' : 'emits';

  // beforePlane is root/mount/.../componentPath OR just componentPath (local)
  const slashParts = beforePlane.split('/').filter((p) => p.length > 0);
  if (slashParts.length === 0) return null;

  const componentPath = slashParts[slashParts.length - 1];
  const beforeComponent = slashParts.slice(0, -1);

  let root: string | undefined;
  let principal: string | undefined;
  const mountSegments: string[] = [];

  if (beforeComponent.length > 0) {
    const firstPart = beforeComponent[0];

    // Split principal@root if @ is present
    const atIdx = firstPart.indexOf('@');
    if (atIdx > 0) {
      principal = firstPart.slice(0, atIdx);
      root = firstPart.slice(atIdx + 1);
    } else {
      root = firstPart;
    }
    mountSegments.push(...beforeComponent.slice(1));

    // Tab normalization inside mount list: root/~tab/<id>/...
    if (mountSegments.length >= 2 && mountSegments[0] === '~tab') {
      const tabId = mountSegments[1];
      root = `${root}.tab.${tabId}`;
      mountSegments.splice(0, 2);
    }
  } else {
    // local-only: <componentPath>.<plane>.<msg>
    root = undefined;
  }

  const toPath = canonicalToPath(mountSegments, componentPath, plane, messageName);

  return {
    scheme: 'matrix',
    raw: uri,
    principal,
    root,
    realm: root,
    mountSegments,
    componentPath,
    plane,
    messageName,
    toPath,
    query,
  };
}
