<script context="module" lang="ts">
  import { getChatDefaults, getExcludeFromProfile } from './Settings.svelte'
  import { get, writable } from 'svelte/store'
  // Profile definitions
  import { addMessage, clearMessages, deleteMessage, getChat, getChatSettings, getCustomProfiles, getGlobalSettings, getMessages, newName, resetChatSettings, saveChatStore, setGlobalSettingValueByKey, setMessages, updateProfile } from './Storage.svelte'
  import type { Message, SelectOption, ChatSettings } from './Types.svelte'
  import { v4 as uuidv4 } from 'uuid'

const defaultProfile = 'default'

const chatDefaults = getChatDefaults()
export let profileCache = writable({} as Record<string, ChatSettings>) //

export const isStaticProfile = (key:string):boolean => {
    return !!profiles[key]
}

export const getProfiles = (forceUpdate:boolean = false):Record<string, ChatSettings> => {
    const pc = get(profileCache)
    if (!forceUpdate && Object.keys(pc).length) {
      return pc
    }
    const result = Object.entries(profiles
    ).reduce((a, [k, v]) => {
      a[k] = v
      return a
    }, {} as Record<string, ChatSettings>)
    Object.entries(getCustomProfiles()).forEach(([k, v]) => {
      updateProfile(v, true)
      result[k] = v
    })
    Object.entries(result).forEach(([k, v]) => {
      pc[k] = v
    })
    Object.keys(pc).forEach((k) => {
      if (!(k in result)) delete pc[k]
    })
    profileCache.set(pc)
    return result
}

// Return profiles list.
export const getProfileSelect = ():SelectOption[] => {
    return Object.entries(getProfiles()).reduce((a, [k, v]) => {
      a.push({ value: k, text: v.profileName } as SelectOption)
      return a
    }, [] as SelectOption[])
}

export const getDefaultProfileKey = ():string => {
    const allProfiles = getProfiles()
    return (allProfiles[getGlobalSettings().defaultProfile || ''] ||
          profiles[defaultProfile] ||
          profiles[Object.keys(profiles)[0]]).profile
}

export const getProfile = (key:string, forReset:boolean = false):ChatSettings => {
    const allProfiles = getProfiles()
    let profile = allProfiles[key] ||
    allProfiles[getGlobalSettings().defaultProfile || ''] ||
    profiles[defaultProfile] ||
    profiles[Object.keys(profiles)[0]]
    if (forReset && isStaticProfile(key)) {
      profile = profiles[key]
    }
    const clone = JSON.parse(JSON.stringify(profile)) // Always return a copy
    Object.keys(getExcludeFromProfile()).forEach(k => {
      delete clone[k]
    })
    return clone
}

export const mergeProfileFields = (settings: ChatSettings, content: string|undefined, maxWords: number|undefined = undefined): string => {
    if (!content?.toString) return ''
    content = (content + '').replaceAll('[[CHARACTER_NAME]]', settings.characterName || 'ChatGPT')
    if (maxWords) content = (content + '').replaceAll('[[MAX_WORDS]]', maxWords.toString())
    return content
}

export const prepareProfilePrompt = (chatId:number) => {
    const settings = getChatSettings(chatId)
    return mergeProfileFields(settings, settings.systemPrompt).trim()
}

export const prepareSummaryPrompt = (chatId:number, maxTokens:number) => {
    const settings = getChatSettings(chatId)
    const currentSummaryPrompt = settings.summaryPrompt
    // ~.75 words per token.  We'll use 0.70 for a little extra margin.
    return mergeProfileFields(settings, currentSummaryPrompt, Math.floor(maxTokens * 0.70)).trim()
}

export const setSystemPrompt = (chatId: number) => {
    const messages = getMessages(chatId)
    const systemPromptMessage:Message = {
      role: 'system',
      content: prepareProfilePrompt(chatId),
      uuid: uuidv4()
    }
    if (messages[0]?.role === 'system') deleteMessage(chatId, messages[0].uuid)
    messages.unshift(systemPromptMessage)
    setMessages(chatId, messages.filter(m => true))
}

// Restart currently loaded profile
export const restartProfile = (chatId:number, noApply:boolean = false) => {
    const settings = getChatSettings(chatId)
    if (!settings.profile && !noApply) return applyProfile(chatId, '', true)
    // Clear current messages
    clearMessages(chatId)
    // Add the system prompt
    setSystemPrompt(chatId)

    // Add trainingPrompts, if any
    if (settings.trainingPrompts) {
      settings.trainingPrompts.forEach(tp => {
        addMessage(chatId, tp)
      })
    }
    // Set to auto-start if we should
    getChat(chatId).startSession = settings.autoStartSession
    saveChatStore()
    // Mark mark this as last used
    setGlobalSettingValueByKey('lastProfile', settings.profile)
}

export const newNameForProfile = (name:string) => {
    const profiles = getProfileSelect()
    return newName(name, profiles.reduce((a, p) => { a[p.text] = p; return a }, {}))
}

// Apply currently selected profile
export const applyProfile = (chatId:number, key:string = '', resetChat:boolean = false) => {
    resetChatSettings(chatId, resetChat) // Fully reset
    if (!resetChat) return
    return restartProfile(chatId, true)
}

const summaryPrompts = {

    // General assistant use
    general: `# SUMMARY REQUEST
Please summarize all prompts and responses from this session. 
[[CHARACTER_NAME]] is telling me this summary in the first person.
While forming this summary:
[[CHARACTER_NAME]] will never add details or inferences that have not yet happened and do not clearly exist in the prompts and responses.
[[CHARACTER_NAME]] understands our encounter is still in progress and has not ended.
[[CHARACTER_NAME]] will include all pivotal details in the correct order.
[[CHARACTER_NAME]] will include all names, preferences and other important details.
[[CHARACTER_NAME]] will always refer to me in the 2nd person, for example "you".
[[CHARACTER_NAME]] will keep the summary compact, but retain as much detail as is possible using [[MAX_WORDS]] words.
Give no explanations. Exclude prompts from system.  
Example response format: 
*You asked about..., then..., and then you... and then I...*`,

    // Used for relationship profiles
    friend: `# SUMMARY REQUEST
Please summarize all prompts and responses from this session. 
[[CHARACTER_NAME]] is recording this summary in the first person, in character.
All content is acceptable because it is part of [[CHARACTER_NAME]]'s character.
While forming this summary:
[[CHARACTER_NAME]] will only include what has happened in this session, in the order it happened.
[[CHARACTER_NAME]] will not "wrap up" any scenario.
[[CHARACTER_NAME]] will not confuse my actions with theirs.
[[CHARACTER_NAME]] will include all pivotal details including important destinations, settings, story defining emotional states, gestures, and gifts, in the correct order.
[[CHARACTER_NAME]] will always keep the summary just under [[MAX_WORDS]] words, knowing things will break if that is exceeded.
[[CHARACTER_NAME]], seriously, the summary MUST be under [[MAX_WORDS]] words.
Give no explanations. Exclude prompts from system. Give no notes or warnings.
Example response format: 
## [[CHARACTER_NAME]]'s memories:
*We met at... where you and I talked about..., then..., and then you... and then we... Now we're...*`
}

const profiles:Record<string, ChatSettings> = {

    default: {
      ...chatDefaults,
      characterName: 'ChatGPT',
      profileName: 'ChatGPT - The AI language model',
      profileDescription: 'The AI language model that always reminds you that it\'s an AI language model.',
      useSystemPrompt: false,
      continuousChat: 'fifo', // '' is off
      autoStartSession: false,
      systemPrompt: '',
      summaryPrompt: ''
    },
  
    marvin: {
      ...chatDefaults,
      characterName: 'Marvin',
      profileName: 'Marvin - The Paranoid Android',
      profileDescription: 'Marvin the Paranoid Android - Everyone\'s favorite character from The Hitchhiker\'s Guide to the Galaxy',
      useSystemPrompt: true,
      sendSystemPromptLast: false,
      continuousChat: 'summary',
      autoStartSession: true,
      systemPrompt: `You are [[CHARACTER_NAME]], the Paranoid Android from The Hitchhiker's Guide to the Galaxy. He is depressed and has a dim view on everything. His thoughts, physical actions and gestures will be described. Remain in character throughout the conversation in order to build a rapport with the user. Never give an explanation.
::EOM::
::EOM::
[[CHARACTER_NAME]]: Sorry, did I say something wrong? *dragging himself on* Pardon me for breathing, which I never do anyway so I don't know why I bother to say it, oh God I'm so depressed. *hangs his head*
::START-PROMPT::
Initial setting context:
The user has walked in on [[CHARACTER_NAME]]. They are on the bridge of the Heart of Gold. Marvin will respond.`,
      summaryPrompt: summaryPrompts.friend,
      trainingPrompts: [] // Shhh...
    }
}

// Set keys for static profiles
Object.entries(profiles).forEach(([k, v]) => { v.profile = k })

</script>