export interface ParameterLike {
  name?: string;
  ref?: string;
  refType?: string;
  resolved?: boolean;
  disconnected?: boolean;
  displayName?: string;
  formLabel?: string;
  dataType?: string;
  children?: ParameterLike[];
}

export interface TokenRange {
  start: number;
  end: number;
  text: string;
  key: string;
}

export type TokenPart =
  | {
      type: 'text';
      text: string;
    }
  | {
      type: 'token';
      text: string;
      key: string;
      valid: boolean;
    };

export interface ParameterCandidate {
  name: string;
  resolved: boolean;
  disconnected?: boolean;
  displayName?: string;
  dataType?: string;
}

const TOKEN_PATTERN = /\{\{\s*([^{}]+?)\s*}}/g;

export function normalizeTokenKey(tokenKey: string): string {
  return tokenKey.trim();
}

export function flattenParameterNames(
  parameters?: ParameterLike[] | null,
): string[] {
  return flattenParameterCandidates(parameters).map((item) => item.name);
}

function isParameterResolved(parameter?: ParameterLike): boolean {
  if (!parameter) {
    return false;
  }

  if (typeof parameter.resolved === 'boolean') {
    return parameter.resolved;
  }

  const refType = (parameter.refType || '').trim();
  if (refType === 'fixed' || refType === 'input') {
    return true;
  }

  const ref = (parameter.ref || '').trim();
  return !!ref;
}

export function flattenParameterCandidates(
  parameters?: ParameterLike[] | null,
): ParameterCandidate[] {
  if (!parameters || parameters.length === 0) {
    return [];
  }

  const candidates: ParameterCandidate[] = [];
  const indexMap = new Map<string, number>();

  const addCandidate = (
    name: string,
    resolved: boolean,
    disconnected: boolean,
    displayName?: string,
    dataType?: string,
  ) => {
    const normalized = name.trim();
    if (!normalized) {
      return;
    }
    const exists = indexMap.get(normalized);
    if (exists === undefined) {
      indexMap.set(normalized, candidates.length);
      candidates.push({
        name: normalized,
        resolved,
        disconnected,
        displayName: displayName?.trim() || normalized,
        dataType,
      });
      return;
    }

    // 同名参数只要有一个可解析，就视为可解析
    const existingCandidate = candidates[exists]!;
    if (resolved) {
      existingCandidate.resolved = true;
      existingCandidate.disconnected = false;
    } else if (disconnected && !existingCandidate.resolved) {
      existingCandidate.disconnected = true;
    }
    if (!existingCandidate.displayName && displayName?.trim()) {
      existingCandidate.displayName = displayName.trim();
    }
    if (!existingCandidate.dataType && dataType) {
      existingCandidate.dataType = dataType;
    }
  };

  const walk = (
    items: ParameterLike[],
    parentPath = '',
    inheritedResolved = true,
  ) => {
    for (const item of items) {
      const rawName = item?.name?.trim();
      if (!rawName) {
        continue;
      }

      const currentPath = parentPath ? `${parentPath}.${rawName}` : rawName;
      const currentResolved = inheritedResolved && isParameterResolved(item);
      const currentDisconnected = item?.disconnected === true;
      const displayName =
        item?.displayName?.trim() ||
        item?.formLabel?.trim() ||
        currentPath;
      addCandidate(
        currentPath,
        currentResolved,
        currentDisconnected,
        displayName,
        item?.dataType,
      );

      if (item.children && item.children.length > 0) {
        walk(item.children, currentPath, currentResolved);
      }
    }
  };

  walk(parameters);
  return candidates;
}

export function parseTokenParts(
  content: string,
  validParams: string[] = [],
): TokenPart[] {
  const source = content ?? '';
  const validSet = new Set(validParams.map(normalizeTokenKey));
  const parts: TokenPart[] = [];

  let lastIndex = 0;
  TOKEN_PATTERN.lastIndex = 0;
  let match: RegExpExecArray | null = TOKEN_PATTERN.exec(source);

  while (match) {
    if (match.index > lastIndex) {
      parts.push({
        type: 'text',
        text: source.slice(lastIndex, match.index),
      });
    }

    const rawToken = match[0];
    const tokenKey = normalizeTokenKey(match[1] || '');
    parts.push({
      type: 'token',
      text: rawToken,
      key: tokenKey,
      valid: validSet.has(tokenKey),
    });

    lastIndex = match.index + rawToken.length;
    match = TOKEN_PATTERN.exec(source);
  }

  if (lastIndex < source.length) {
    parts.push({
      type: 'text',
      text: source.slice(lastIndex),
    });
  }

  if (parts.length === 0) {
    parts.push({
      type: 'text',
      text: source,
    });
  }

  return parts;
}

export function getTokenRanges(content: string): TokenRange[] {
  const source = content ?? '';
  const ranges: TokenRange[] = [];
  TOKEN_PATTERN.lastIndex = 0;
  let match: RegExpExecArray | null = TOKEN_PATTERN.exec(source);

  while (match) {
    const rawToken = match[0];
    ranges.push({
      start: match.index,
      end: match.index + rawToken.length,
      text: rawToken,
      key: normalizeTokenKey(match[1] || ''),
    });
    match = TOKEN_PATTERN.exec(source);
  }

  return ranges;
}

export function findTokenRangeAtCursor(
  content: string,
  cursor: number,
  options?: {
    includeStart?: boolean;
    includeEnd?: boolean;
  },
): TokenRange | null {
  if (!Number.isInteger(cursor) || cursor < 0) {
    return null;
  }

  const includeStart = options?.includeStart ?? false;
  const includeEnd = options?.includeEnd ?? false;
  const ranges = getTokenRanges(content);
  for (const range of ranges) {
    const leftValid = includeStart
      ? cursor >= range.start
      : cursor > range.start;
    const rightValid = includeEnd ? cursor <= range.end : cursor < range.end;
    if (leftValid && rightValid) {
      return range;
    }
  }

  return null;
}

export function findBackspaceTokenRange(
  content: string,
  cursor: number,
): TokenRange | null {
  return findTokenRangeAtCursor(content, cursor, {
    includeStart: false,
    includeEnd: true,
  });
}

export function splitTokenDisplay(
  rawToken: string,
  normalizedKey?: string,
): {
  hiddenPrefix: string;
  visibleText: string;
  hiddenSuffix: string;
} {
  const source = rawToken ?? '';
  if (!source.startsWith('{{') || !source.endsWith('}}')) {
    return {
      hiddenPrefix: '',
      visibleText: normalizedKey || source,
      hiddenSuffix: '',
    };
  }

  const inner = source.slice(2, -2);
  const visibleText = normalizeTokenKey(normalizedKey || inner);
  if (!visibleText) {
    return {
      hiddenPrefix: '',
      visibleText: source,
      hiddenSuffix: '',
    };
  }

  const innerStart = inner.indexOf(visibleText);
  const leading = innerStart >= 0 ? inner.slice(0, innerStart) : '';
  const trailing =
    innerStart >= 0 ? inner.slice(innerStart + visibleText.length) : '';

  return {
    hiddenPrefix: `{{${leading}`,
    visibleText,
    hiddenSuffix: `${trailing}}}`,
  };
}

export function insertTextAtCursor(
  content: string,
  insertedText: string,
  selectionStart?: number | null,
  selectionEnd?: number | null,
): {
  value: string;
  cursor: number;
} {
  const source = content ?? '';
  const start = Number.isInteger(selectionStart)
    ? Math.max(0, Math.min(selectionStart as number, source.length))
    : source.length;
  const end = Number.isInteger(selectionEnd)
    ? Math.max(start, Math.min(selectionEnd as number, source.length))
    : start;

  const nextValue = source.slice(0, start) + insertedText + source.slice(end);
  return {
    value: nextValue,
    cursor: start + insertedText.length,
  };
}

export function escapeHtml(text: string): string {
  return text
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#39;');
}
