// === Generated by Qwen3-Max ===

/**
 * 元となるURLと相対URLから絶対URLを解決します。
 * ベースURLがファイルパスを含む場合でも、そのファイルのディレクトリを基準に解決します。
 *
 * @param baseUrl - 元となるURL（絶対URL）
 * @param relativeUrl - 相対URLまたは絶対URL
 * @returns 解決された絶対URL
 * @throws URLが無効な場合にエラーをスローします
 */
function resolveUrl(baseUrl: string, relativeUrl: string): string {
  try {
    // 相対URLがすでに絶対URLの場合、そのまま返す
    if (isAbsoluteUrl(relativeUrl)) {
      return new URL(relativeUrl).href;
    }

    // baseUrlをURLとしてパース
    const base = new URL(baseUrl);

    // ベースURLがファイルパス（末尾が/でない）の場合、
    // ファイル名を除去してディレクトリパスにする
    if (!base.pathname.endsWith('/')) {
      // 最後のスラッシュ以降を除去（ファイル名を削除）
      const lastSlashIndex = base.pathname.lastIndexOf('/');
      if (lastSlashIndex !== -1) {
        base.pathname = base.pathname.substring(0, lastSlashIndex + 1);
      } else {
        // パスにスラッシュが含まれていない稀なケース
        base.pathname = '/';
      }
    }

    // 相対URLを解決
    const resolved = new URL(relativeUrl, base);

    return resolved.href;
  } catch (error) {
    throw new Error(`URLの解決に失敗しました: ${error instanceof Error ? error.message : 'Unknown error'}`);
  }
}

/**
 * URLが絶対URLかどうかを判定します。
 *
 * @param url - 判定するURL文字列
 * @returns 絶対URLの場合はtrue、それ以外はfalse
 */
function isAbsoluteUrl(url: string): boolean {
  try {
    return Boolean(new URL(url).protocol);
  } catch {
    return false;
  }
}

/**
 * URLフルパスからファイル名を除いた部分を抽出します
 * @param url
 * @returns
 */
function getBaseUrlWithoutLastSegment(url: string): string {
  const u = new URL(url);
  const dirPath = u.pathname.split('/').slice(0, -1).join('/');
  return `${u.origin}${dirPath}`;
}

function removeQueryStr(url: string): string {
  const urlObj = new URL(url);
  urlObj.search = '';
  return urlObj.toString();
}

// ==============================

function replaceMultiPatterns(replacements: [RegExp, string][], originalString: string): string {
  return replacements.reduce((currentString, [pattern, replacement]) => {
    return currentString.replace(pattern, replacement);
  }, originalString);
}

function sanitizeFilename(filename: string) {
  const invalidCharsMap: Record<string, string> = {
    '\\': '＼',
    '/': '／',
    ':': '：',
    '*': '＊',
    '?': '？',
    '"': '＂',
    '<': '＜',
    '>': '＞',
    '|': '｜',
    '&': '＆',
  };
  return Array.from(filename)
    .map((char) => invalidCharsMap[char] || char)
    .join('');
}

/**
 * 文字列が指定された正規表現パターンにマッチするかを判定する関数
 *
 * @param inputStr - チェック対象の文字列
 * @param regexInclude - マッチが必須の正規表現パターン（少なくとも1つマッチする必要あり）
 * @param regexExclude - マッチが禁止の正規表現パターン（1つもマッチしてはならない）
 * @returns
 *   includeパターンが空の場合はexcludeにマッチしない限りtrue
 *   includeパターンがある場合は、少なくとも1つのincludeにマッチし、
 *   かつ全てのexcludeにマッチしない場合のみtrue
 */
function filterByRegex(
  inputStr: string,
  regexInclude: readonly (RegExp | string)[],
  regexExclude: readonly (RegExp | string)[],
): boolean {
  // 文字列をRegExpに変換するヘルパー関数（グローバルフラグを無効化）
  const toRegExp = (pattern: RegExp | string): RegExp => {
    if (pattern instanceof RegExp) {
      // グローバルフラグ/ステッキーフラグを無効化（test()の状態問題を回避）
      const flags = pattern.flags.replace(/[gy]/g, '');
      return new RegExp(pattern.source, flags);
    }
    return new RegExp(pattern);
  };

  // 正規表現を変換（include/exclude両方）
  const includePatterns = regexInclude.map(toRegExp);
  const excludePatterns = regexExclude.map(toRegExp);

  // 1. excludeパターンに1つでもマッチしたら即座にfalse
  if (excludePatterns.some((pattern) => pattern.test(inputStr))) {
    return false;
  }

  // 2. includeパターンが空の場合はtrue（excludeを通過した時点でOK）
  if (includePatterns.length === 0) {
    return true;
  }

  // 3. includeパターンに1つでもマッチすればtrue
  return includePatterns.some((pattern) => pattern.test(inputStr));
}

// ==============================

const mdLinkTextGenFn = (text: string, uri: string) => `[${text}](${uri})`;

export default {
  resolveUrl,
  isAbsoluteUrl,
  getBaseUrlWithoutLastSegment,
  removeQueryStr,
  replaceMultiPatterns,
  sanitizeFilename,
  filterByRegex,
  mdLinkTextGenFn,
};
