summaryrefslogtreecommitdiff
path: root/src/actions/prosody.ts
blob: 9ec5dd2b48196a97549e576ed3bfcc793c50ccfe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"use server";

import pdb from "@/lib/db/prosodydb";
import { WordData } from "@/zoom/logic/types";

export async function getOnsets(onset: string) {
  const data = pdb.fetchOnsets(onset);
  return data;
}
// Helper to extract tone from prosody - assuming prosody is an array of objects like [{tone: number}, ...]
const getTonesFromProsody = (prosody: any): number[] | null => {
  if (Array.isArray(prosody) && prosody.length > 0) {
    return prosody.map((p) => p.tone).filter((t) => typeof t === "number");
  }
  return null;
};

export async function fetchWordsByToneAndSyllables(
  syllableCount: number,
  tones: (number | null)[], // Array of tones, one for each syllable. null means any tone.
): Promise<WordData | null> {
  if (syllableCount !== tones.length) {
    console.error("Syllable count and tones array length mismatch");
    return null;
  }

  const queryParams: (string | number)[] = ["th", syllableCount, syllableCount]; // lang, syllables (for WHERE), syllables (for json_array_length)
  let toneConditions = "";

  const toneClauses: string[] = [];
  tones.forEach((tone, index) => {
    if (tone !== null && typeof tone === "number") {
      // Assumes SQLite's json_extract function is available and prosody is like: [{"tone": 1}, {"tone": 3}, ...]
      // Path for first syllable's tone: '$[0].tone'
      toneClauses.push(`json_extract(prosody, '$[${index}].tone') = ?`);
      queryParams.push(tone);
    }
  });

  if (toneClauses.length > 0) {
    toneConditions = `AND ${toneClauses.join(" AND ")}`;
  }

  const queryString = `
    SELECT id, spelling, prosody, syllables, lang, type, frequency, confidence, ipa,
      (SELECT
        json_group_array(json_object(
          'pos', pos,
          'senses', s.senses,
          'forms', forms,
          'etymology', etymology,
          'related', related)
        ) FROM senses s WHERE s.parent_id = expressions.id
      ) as senses_array
    FROM expressions
    WHERE lang = ?
      AND syllables = ?
      AND type = 'word'
      AND json_valid(prosody)
      AND json_array_length(prosody) = ? -- Ensures prosody array has correct number of elements
      ${toneConditions}
    ORDER BY RANDOM() -- Get a random word matching criteria
    LIMIT 1
  `;

  try {
    const query = db.db.query(queryString);
    const row = query.get(...queryParams) as any;

    if (!row) return null;

    // Map to WordData (simplified, similar to initial fetch in tones.tsx or db.fetchWordBySpelling)
    // This mapping might need to be more robust depending on actual WordData requirements.
    const word: WordData = {
      id: row.id,
      spelling: row.spelling,
      prosody: JSON.parse(row.prosody),
      syllables: row.syllables,
      lang: row.lang,
      type: row.type,
      frequency: row.frequency,
      confidence: row.confidence,
      ipa: row.ipa ? JSON.parse(row.ipa) : [],
      // Senses parsing is simplified here. Adjust if full sense data is needed.
      senses: row.senses_array
        ? JSON.parse(row.senses_array).map((s: any) => ({
            pos: s.pos,
            senses:
              typeof s.senses === "string" ? JSON.parse(s.senses) : s.senses,
            forms: typeof s.forms === "string" ? JSON.parse(s.forms) : s.forms,
            etymology: s.etymology,
            related:
              typeof s.related === "string" ? JSON.parse(s.related) : s.related,
          }))
        : [],
    };
    return word;
  } catch (error) {
    console.error("Error fetching word by tone and syllables:", error);
    console.error("Query:", queryString);
    console.error("Params:", queryParams);
    return null;
  }
}