/**
 * ParsedMatrixUri - URI parser per federation DESIGN.md SSOT
 *
 * NOTE: Named ParsedMatrixUri (not MatrixUri) to avoid conflict with
 * existing MatrixUri interface in ../uri.ts
 *
 * Format per federation DESIGN.md section 2.1:
 *   <root>/<mount...>/<component.path>.<plane>.<message>
 *
 * IMPORTANT: The root is the FULL authority segment (e.g., "daemon@matrix-3").
 * The @ character is part of the root identity, NOT a principal separator.
 * This matches federation DESIGN.md - the canonical SSOT for this package.
 *
 * Component path vs mount distinction:
 * - Mount segments use "/" separator
 * - Component path uses "." separator as FINAL path segment
 * - Last segment with dots is component path; rest is mount
 */

import { RootId } from './root_id';

// FEDERATION DESIGN.md SSOT: root is the FULL authority (including @)
// e.g., "daemon@matrix-3" is the root, NOT split into principal/root
const MATRIX_URI_REGEX = /^matrix:\/\/([^/?]+)(?:\/([^?]*))?(?:\?(.*))?$/;
const SEGMENT_PATTERN = /^[a-zA-Z0-9_.@-]+$/;

export interface ParsedMatrixUriComponents {
  root: RootId;
  /** @deprecated Use root instead */
  realm?: RootId;
  principal?: string;
  mount?: string;
  componentPath?: string;
  query?: Map<string, string>;
}

export class ParsedMatrixUri {
  private constructor(
    private readonly _root: RootId,
    private readonly _principal: string | undefined,
    private readonly _mount: string | undefined,
    private readonly _componentPath: string | undefined,
    private readonly _query: Map<string, string>
  ) {}

  /**
   * Parse a Matrix URI string.
   * Throws if the URI is malformed.
   */
  static parse(uri: string): ParsedMatrixUri {
    if (typeof uri !== 'string') {
      throw new Error('ParsedMatrixUri.parse requires a string');
    }

    const match = uri.match(MATRIX_URI_REGEX);
    if (!match) {
      throw new Error(`Invalid Matrix URI: "${uri}"`);
    }

    // FEDERATION DESIGN.md SSOT: root is the FULL authority (including @)
    const [, rootStr, pathStr, queryStr] = match;

    // Validate and create root (@ is now allowed in root)
    const root = RootId.of(rootStr);

    // Principal is NOT extracted - per SSOT, @ is part of root identity
    const principal = undefined;

    // Parse path into mount and component path
    let mount: string | undefined;
    let componentPath: string | undefined;

    if (pathStr) {
      const segments = pathStr.split('/').filter(s => s.length > 0);

      if (segments.length > 0) {
        // Validate all segments
        for (const seg of segments) {
          if (!SEGMENT_PATTERN.test(seg)) {
            throw new Error(`Invalid path segment in Matrix URI: "${seg}"`);
          }
        }

        // Last segment with dots is component path (deterministic rule)
        const lastSegment = segments[segments.length - 1];
        if (lastSegment.includes('.')) {
          componentPath = lastSegment;
          mount = segments.slice(0, -1).join('/');
        } else {
          mount = segments.join('/');
        }

        // Normalize empty mount to undefined
        if (mount === '') {
          mount = undefined;
        }
      }
    }

    // Parse query string
    const query = new Map<string, string>();
    if (queryStr) {
      const params = new URLSearchParams(queryStr);
      for (const [key, value] of params) {
        query.set(key, value);
      }
    }

    return new ParsedMatrixUri(root, principal, mount, componentPath, query);
  }

  /**
   * Try to parse a URI, returning null if invalid.
   */
  static tryParse(uri: string): ParsedMatrixUri | null {
    try {
      return ParsedMatrixUri.parse(uri);
    } catch {
      return null;
    }
  }

  /**
   * Build a ParsedMatrixUri from components.
   */
  static build(components: ParsedMatrixUriComponents): ParsedMatrixUri {
    const root = components.root ?? components.realm;
    if (!root) throw new Error('ParsedMatrixUri.build requires root (or realm) in components');
    return new ParsedMatrixUri(
      root,
      components.principal,
      components.mount,
      components.componentPath,
      components.query ?? new Map()
    );
  }

  // Getters
  get root(): RootId {
    return this._root;
  }

  /** @deprecated Use .root instead */
  get realm(): RootId {
    return this._root;
  }

  get principal(): string | undefined {
    return this._principal;
  }

  get mount(): string | undefined {
    return this._mount;
  }

  get componentPath(): string | undefined {
    return this._componentPath;
  }

  get query(): Map<string, string> {
    return new Map(this._query);
  }

  /**
   * Get the exec query parameter if present.
   */
  get exec(): string | undefined {
    return this._query.get('exec');
  }

  /**
   * Get the full path (mount + component path).
   */
  get fullPath(): string {
    const parts: string[] = [];
    if (this._mount) parts.push(this._mount);
    if (this._componentPath) parts.push(this._componentPath);
    return parts.join('/');
  }

  /**
   * Serialize back to URI string.
   */
  toString(): string {
    let uri = '';

    if (this._principal) {
      uri += `${this._principal}@`;
    }

    uri += this._root.value;

    const fullPath = this.fullPath;
    if (fullPath) {
      uri += `/${fullPath}`;
    }

    if (this._query.size > 0) {
      const params = new URLSearchParams();
      for (const [key, value] of this._query) {
        params.set(key, value);
      }
      uri += `?${params.toString()}`;
    }

    return uri;
  }

  toJSON(): {
    root: string;
    /** @deprecated */
    realm: string;
    principal?: string;
    mount?: string;
    componentPath?: string;
    query?: Record<string, string>;
  } {
    const obj: ReturnType<ParsedMatrixUri['toJSON']> = {
      root: this._root.value,
      realm: this._root.value,
    };
    if (this._principal) obj.principal = this._principal;
    if (this._mount) obj.mount = this._mount;
    if (this._componentPath) obj.componentPath = this._componentPath;
    if (this._query.size > 0) {
      obj.query = Object.fromEntries(this._query);
    }
    return obj;
  }
}

export function isParsedMatrixUri(value: unknown): value is ParsedMatrixUri {
  return value instanceof ParsedMatrixUri;
}
