{
  "version": 3,
  "sources": ["../../../src/Tag.ts"],
  "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Schema from 'effect/Schema';\n\nimport type { ForeignKey } from '@dxos/echo-protocol';\n\nimport type * as Database from './Database';\nimport * as Filter from './Filter';\nimport * as internal from './internal';\nimport * as Obj from './Obj';\nimport * as Type from './Type';\n\nexport const Tag = Schema.Struct({\n  label: Schema.String,\n  hue: Schema.optional(Schema.String), // TODO(burdon): Color name?\n}).pipe(\n  internal.LabelAnnotation.set(['label']),\n  internal.HiddenAnnotation.set(true),\n  // Shared DXN so `meta.tags` (the `Ref<Tag>` schema) and this type stay in sync.\n  Type.makeObject(internal.TagTypeDXN),\n);\n\nexport type Tag = Type.InstanceType<typeof Tag>;\n\nexport const make = (props: Obj.MakeProps<typeof Tag>) => Obj.make(Tag, props);\n\nexport type Map = Record<string, Tag>;\n\nexport const sortTags = ({ label: a }: Tag, { label: b }: Tag) => a.localeCompare(b);\n\nexport const createTagList = (tags: Map): Tag[] =>\n  Object.entries(tags)\n    .map(([id, tag]) => ({ ...tag, id }))\n    .sort(sortTags);\n\nexport const findTagByLabel = (tags: Map | undefined, name: string): Tag | undefined => {\n  const entry = Object.entries(tags ?? {}).find(([_, tag]) => tag.label.toLowerCase() === name.toLowerCase());\n  return entry ? { ...entry[1], id: entry[0] } : undefined;\n};\n\n/**\n * Finds or creates a {@link Tag} object in the database.\n *\n * - With a foreign `key` (system/provider tags): matched by that key; the label is kept current on\n *   re-sync. Use a stable key (e.g. `{ source: 'google.com/gmail/label', id }`) for tags whose\n *   identity is external or well-known.\n * - Without a key (user tags): matched by case-insensitive label among tags that carry **no**\n *   foreign key, so it never collides with a keyed system/provider tag of the same label.\n */\nexport const findOrCreate = async (\n  db: Pick<Database.Database, 'query' | 'add'>,\n  options: { label: string; hue?: string; key?: ForeignKey },\n): Promise<Tag> => {\n  const { label, hue, key } = options;\n  const withHue = hue ? { hue } : {};\n  if (key) {\n    const [existing] = await db.query(Filter.foreignKeys(Tag, [key])).run();\n    if (existing) {\n      // Keep label (and hue, when provided) current on re-sync. `hue` is only touched when supplied,\n      // so a label-only re-sync (e.g. Gmail) doesn't wipe a user-set colour.\n      if (existing.label !== label || (hue !== undefined && existing.hue !== hue)) {\n        Obj.update(existing, (existing) => {\n          existing.label = label;\n          if (hue !== undefined) {\n            existing.hue = hue;\n          }\n        });\n      }\n      return existing;\n    }\n    return db.add(Obj.make(Tag, { [Obj.Meta]: { keys: [key] }, label, ...withHue }));\n  }\n\n  const lowered = label.toLowerCase();\n  const candidates = await db.query(Filter.type(Tag)).run();\n  const existing = candidates.find(\n    (tag) => tag.label.toLowerCase() === lowered && (Obj.getMeta(tag).keys ?? []).length === 0,\n  );\n  return existing ?? db.add(make({ label, ...withHue }));\n};\n"],
  "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;cAAAA;EAAA;;AAMA,YAAYC,YAAY;AAUjB,IAAMC,MAAaC,cAAO;EAC/BC,OAAcC;EACdC,KAAYC,gBAAgBF,aAAM;AACpC,CAAA,EAAGG;EACQC,gBAAgBC,IAAI;IAAC;GAAQ;EAC7BC,iBAAiBD,IAAI,IAAA;;EAEzBE,WAAoBC,UAAU;AAAA;AAK9B,IAAMC,QAAO,CAACC,UAAyCD,KAAKZ,KAAKa,KAAAA;AAIjE,IAAMC,WAAW,CAAC,EAAEZ,OAAOa,EAAC,GAAS,EAAEb,OAAOc,EAAC,MAAYD,EAAEE,cAAcD,CAAAA;AAE3E,IAAME,gBAAgB,CAACC,SAC5BC,OAAOC,QAAQF,IAAAA,EACZG,IAAI,CAAC,CAACC,IAAIC,GAAAA,OAAU;EAAE,GAAGA;EAAKD;AAAG,EAAA,EACjCE,KAAKX,QAAAA;AAEH,IAAMY,iBAAiB,CAACP,MAAuBQ,SAAAA;AACpD,QAAMC,QAAQR,OAAOC,QAAQF,QAAQ,CAAC,CAAA,EAAGU,KAAK,CAAC,CAACC,GAAGN,GAAAA,MAASA,IAAItB,MAAM6B,YAAW,MAAOJ,KAAKI,YAAW,CAAA;AACxG,SAAOH,QAAQ;IAAE,GAAGA,MAAM,CAAA;IAAIL,IAAIK,MAAM,CAAA;EAAG,IAAII;AACjD;AAWO,IAAMC,eAAe,OAC1BC,IACAC,YAAAA;AAEA,QAAM,EAAEjC,OAAOE,KAAKgC,IAAG,IAAKD;AAC5B,QAAME,UAAUjC,MAAM;IAAEA;EAAI,IAAI,CAAC;AACjC,MAAIgC,KAAK;AACP,UAAM,CAACE,SAAAA,IAAY,MAAMJ,GAAGK,MAAaC,YAAYxC,KAAK;MAACoC;KAAI,CAAA,EAAGK,IAAG;AACrE,QAAIH,WAAU;AAGZ,UAAIA,UAASpC,UAAUA,SAAUE,QAAQ4B,UAAaM,UAASlC,QAAQA,KAAM;AAC3EsC,QAAIC,OAAOL,WAAU,CAACA,cAAAA;AACpBA,UAAAA,UAASpC,QAAQA;AACjB,cAAIE,QAAQ4B,QAAW;AACrBM,YAAAA,UAASlC,MAAMA;UACjB;QACF,CAAA;MACF;AACA,aAAOkC;IACT;AACA,WAAOJ,GAAGU,IAAQhC,KAAKZ,KAAK;MAAE,CAAK6C,IAAI,GAAG;QAAEC,MAAM;UAACV;;MAAK;MAAGlC;MAAO,GAAGmC;IAAQ,CAAA,CAAA;EAC/E;AAEA,QAAMU,UAAU7C,MAAM6B,YAAW;AACjC,QAAMiB,aAAa,MAAMd,GAAGK,MAAaU,KAAKjD,GAAAA,CAAAA,EAAMyC,IAAG;AACvD,QAAMH,WAAWU,WAAWnB,KAC1B,CAACL,QAAQA,IAAItB,MAAM6B,YAAW,MAAOgB,YAAgBG,QAAQ1B,GAAAA,EAAKsB,QAAQ,CAAA,GAAIK,WAAW,CAAA;AAE3F,SAAOb,YAAYJ,GAAGU,IAAIhC,MAAK;IAAEV;IAAO,GAAGmC;EAAQ,CAAA,CAAA;AACrD;",
  "names": ["make", "Schema", "Tag", "Struct", "label", "String", "hue", "optional", "pipe", "LabelAnnotation", "set", "HiddenAnnotation", "makeObject", "TagTypeDXN", "make", "props", "sortTags", "a", "b", "localeCompare", "createTagList", "tags", "Object", "entries", "map", "id", "tag", "sort", "findTagByLabel", "name", "entry", "find", "_", "toLowerCase", "undefined", "findOrCreate", "db", "options", "key", "withHue", "existing", "query", "foreignKeys", "run", "Obj", "update", "add", "Meta", "keys", "lowered", "candidates", "type", "getMeta", "length"]
}
