/// 文字列類似度ユーティリティ
///
/// さけのわ銘柄マッチングなどで使用
class StringSimilarity {
  /// Levenshtein距離（編集距離）を計算
  ///
  /// 文字列s1をs2に変換するために必要な最小編集回数
  /// 編集: 挿入、削除、置換
  ///
  /// 例:
  /// - levenshteinDistance("獺祭", "だっさい") = 3
  /// - levenshteinDistance("獺祭", "獺祭") = 0
  static int levenshteinDistance(String s1, String s2) {
    if (s1 == s2) return 0;
    if (s1.isEmpty) return s2.length;
    if (s2.isEmpty) return s1.length;

    final len1 = s1.length;
    final len2 = s2.length;

    // DP table: (len1+1) x (len2+1)
    final matrix = List.generate(
      len1 + 1,
      (i) => List.filled(len2 + 1, 0),
    );

    // 初期化
    for (int i = 0; i <= len1; i++) {
      matrix[i][0] = i;
    }
    for (int j = 0; j <= len2; j++) {
      matrix[0][j] = j;
    }

    // DP処理
    for (int i = 1; i <= len1; i++) {
      for (int j = 1; j <= len2; j++) {
        final cost = s1[i - 1] == s2[j - 1] ? 0 : 1;

        matrix[i][j] = _min3(
          matrix[i - 1][j] + 1, // 削除
          matrix[i][j - 1] + 1, // 挿入
          matrix[i - 1][j - 1] + cost, // 置換
        );
      }
    }

    return matrix[len1][len2];
  }

  /// 類似度スコアを計算（0.0-1.0）
  ///
  /// 1.0に近いほど類似
  /// 0.0は全く異なる
  ///
  /// 計算式: 1.0 - (編集距離 / 長い方の文字列長)
  static double similarity(String s1, String s2) {
    if (s1 == s2) return 1.0;
    if (s1.isEmpty || s2.isEmpty) return 0.0;

    final distance = levenshteinDistance(s1, s2);
    final maxLength = s1.length > s2.length ? s1.length : s2.length;

    return 1.0 - (distance / maxLength);
  }

  /// 正規化された類似度（全角半角・大文字小文字を無視）
  ///
  /// 日本酒銘柄のマッチングに最適
  ///
  /// 例:
  /// - normalizedSimilarity("獺祭", "だっさい") ≈ 0.0 (漢字とひらがなは別物)
  /// - normalizedSimilarity("DASSAI", "dassai") = 1.0 (大文字小文字無視)
  /// - normalizedSimilarity("獺祭　純米大吟醸", "獺祭 純米大吟醸") ≈ 1.0 (スペース正規化)
  static double normalizedSimilarity(String s1, String s2) {
    // 正規化処理
    final normalized1 = _normalize(s1);
    final normalized2 = _normalize(s2);

    return similarity(normalized1, normalized2);
  }

  /// 文字列の正規化
  ///
  /// - 小文字化
  /// - 全角スペース→半角スペース
  /// - 連続スペース→単一スペース
  /// - 前後のスペース削除
  static String _normalize(String str) {
    return str
        .toLowerCase()
        .replaceAll('　', ' ') // 全角スペース→半角
        .replaceAll(RegExp(r'\s+'), ' ') // 連続スペース→単一
        .trim();
  }

  /// 部分一致スコア（s2がs1に含まれているか）
  ///
  /// 例:
  /// - containsSimilarity("獺祭 純米大吟醸45", "獺祭") = 1.0
  /// - containsSimilarity("八海山", "獺祭") = 0.0
  static double containsSimilarity(String haystack, String needle) {
    final normalizedHaystack = _normalize(haystack);
    final normalizedNeedle = _normalize(needle);

    if (normalizedNeedle.isEmpty) return 0.0;
    if (normalizedHaystack.contains(normalizedNeedle)) return 1.0;

    // 部分一致しない場合は通常の類似度
    return normalizedSimilarity(haystack, needle);
  }

  /// 3つの値の最小値を返す
  static int _min3(int a, int b, int c) {
    return a < b ? (a < c ? a : c) : (b < c ? b : c);
  }

  /// さけのわ銘柄マッチング専用スコア
  ///
  /// 日本酒銘柄の特性に最適化:
  /// 1. 完全一致: 1.0
  /// 2. 正規化後一致: 0.95
  /// 3. 部分一致（銘柄名を含む）: 0.9
  /// 4. 高類似度（0.8以上）: そのまま
  /// 5. それ以外: 類似度スコア
  static double sakenowaMatchScore(String geminiName, String sakenowaName) {
    // 完全一致
    if (geminiName == sakenowaName) return 1.0;

    // 正規化
    final normalizedGemini = _normalize(geminiName);
    final normalizedSakenowa = _normalize(sakenowaName);

    // 正規化後一致
    if (normalizedGemini == normalizedSakenowa) return 0.95;

    // 部分一致チェック（どちらかが含まれている）
    if (normalizedGemini.contains(normalizedSakenowa) ||
        normalizedSakenowa.contains(normalizedGemini)) {
      return 0.9;
    }

    // 類似度計算
    return normalizedSimilarity(geminiName, sakenowaName);
  }
}
