
    Gil              	           d Z ddlZddlZddlmZ ddlmZmZmZm	Z	 ddl
m
Z
 ddlmZmZmZmZ ddd	ed
eefdefdZddd	ed
eefdedefdZddd	ed
eefdedefdZ G d d      Zy)a  Configuration manager for Play Palace client.

Handles client-side configuration including:
- Server management with user accounts (identities.json - private)
- Global default options (option_profiles.json - shareable)
- Per-server option overrides (option_profiles.json - shareable)
    N)Path)DictAnyOptionalList)datetime)
IdentitiesServerUserAccountvalidate_identitiesFcreate_mode
dictionarykey_pathr   c          
      l   t        |t              r9t        |      dkD  r+|d   dk(  r|dd }|d   dk(  r|dd }|j                  d      }| }t	        t        |            D ]Q  }||   dk(  r||   }||vr8|s1t        d| d|dkD  rd	dj                  |d|       z   nd
z   dz         i ||<   ||   }S |S )zReturn the item in a dictionary, typically a nested layer dict.
    Optionally create keys that don't exist, or require the full path to exist already.
    This function supports an infinite number of layers.r   /   N Key 'z	' not in znested dictionary zroot dictionary.)
isinstancestrlensplitrangeKeyErrorjoin)r   r   r   scopellayers         =C:\Users\dbart\PlayPalace11\clients\desktop\config_manager.pyget_item_from_dictr#      s     (C S]Q%6A;#|HB<3}H>>#&E3x=!A;"E7), q5 ."10FF.	   E%Le! "" L    returnc                   t        |t              r9t        |      dkD  r+|d   dk(  r|dd }|d   dk(  r|dd }|j                  d      }|r|d   dk(  rt	        d      |j                  d      }t        | ||      }t        |t              st        d	t        |       d
      |s||vrt        d| d| d
      |||<   y)zModify the value of an item in a dictionary.
    Optionally create keys that don't exist, or require the full path to exist already.
    This function supports an infinite number of layers.r   r   r   Nr   r   %No dictionary key path was specified.r   #Expected type 'dict', instead got ''.r   z' not in dictionary 'T)r   r   r   r   
ValueErrorpopr#   dict	TypeErrortyper   )r   r   valuer   	final_keyobjs         r"   set_item_in_dictr2   1   s     (C S]Q%6A;#|HB<3}H>>#&x|r)@AAR I
Z{
KCc4 =d3i[KLL9C/yk)>xjKLLC	Nr$   Tdelete_empty_layersr4   c                H   t        |t              r9t        |      dkD  r+|d   dk(  r|dd }|d   dk(  r|dd }|j                  d      }|r|d   dk(  rt	        d      |j                  d      }t        | |      }t        |t              st        dt        |       d	      ||vry
||= |syt        t        |      dd      D ]K  }	 t        | |d|       }t        |t              r)|s'|dk(  r| |d   = nt        | |d|dz
         }|||dz
     = M y# t        $ r Y  yw xY w)zDelete an item in a dictionary.
    Optionally delete layers that are empty.
    This function supports an infinite number of layers.r   r   r   Nr   r   r'   r(   r)   FT)r   r   r   r   r*   r+   r#   r,   r-   r.   r   r   )r   r   r4   r0   r1   iparents          r"   delete_item_from_dictr8   I   sU    (C S]Q%6A;#|HB<3}H>>#&x|r)@AAR I
Z
2Cc4 =d3i[KLLI3x=!R(		$Z"1>C#t$S6"8A;//
HWq1u<MNFxA/ )   		s   	AD	D! D!c                      e Zd ZdZdLdee   fdZdeee	f   fdZ
deee	f   fdZdeee	f   fdZdeee	f   fd	Zd
eee	f   deee	f   fdZd
eee	f   defdZd
eee	f   defdZd
eee	f   defdZd
eee	f   defdZ	 dLdeee	f   dededz  defdZd
eee	f   defdZd
eee	f   defdZedeee	f   deee	f   deee	f   fd       Z	 dMdeee	f   deee	f   dedeee	f   fdZd Zd Zd  Zdee   fd!Zd"edee   fd#Zd"edeeee	f      fd$Zdeeeee	f   f   fd%Z 	 dNd&ed'ed(e!d)edef
d*Z"	 	 	 	 dOd"ed&ee   d'ee   d(ee!   d)ee   f
d+Z#d"efd,Z$d"edefd-Z%d"edee   fd.Z&d"efd/Z'd"edeeee	f      fd0Z(d"ed1eee	f   ddfd2Z)d"eddfd3Z*d"edeeeee	f   f   fd4Z+d"ed5edeeee	f      fd6Z,	 	 	 	 dPd"ed7ed8ed9ed)ed:ed;ee!   dee   fd<Z-	 	 	 	 	 	 dQd"ed5ed7ee   d8ee   d:ee   d;ee!   d9ee   d)ee   fd=Z.d"ed5efd>Z/d"ed5efd?Z0dLd"ee   deee	f   fd@Z1	 dLdAdBdCedDe	d"ee   dEefdFZ2ddGd"edCedHefdIZ3dJe	de	fdKZ4y)RConfigManagerzManages client configuration and per-server settings.

    Uses two separate files:
    - identities.json: Contains servers with user accounts (private, not shareable)
    - option_profiles.json: Contains client options (shareable, no credentials)
    N	base_pathc                     |t        j                         dz  }|| _        |dz  | _        |dz  | _        | j                         | _        | j                         | _        y)z}Initialize the config manager.

        Args:
            base_path: Base directory path. Defaults to ~/.playpalace/
        Nz.playpalacezidentities.jsonzoption_profiles.json)	r   homer;   identities_pathprofiles_path_load_identities
identities_load_profilesprofiles)selfr;   s     r"   __init__zConfigManager.__init__w   s]     		m3I"(+<<&)??//1++-r$   r%   c                    | j                   j                         rj	 t        | j                   d      5 }t        j                  |      }t        |      }||k7  r"|| _        | j                          t        d       |cddd       S | j                         S # 1 sw Y   nxY w	 | j                         S # t        $ r(}t        d|        | j                         cY d}~S d}~ww xY w)z7Load identities from file (servers with user accounts).rzIdentities validated and saved.NzError loading identities: )r>   existsopenjsonloadr   rA   save_identitiesprint	Exception_get_default_identities)rD   fraw	validatedes        r"   r@   zConfigManager._load_identities   s    &&(6$..4))A,C 3C 8I C'*3,,.?@$% % ++-- 544 ++--	  621#6733556s5   B2 A	B;	B2 BB2 2	C#;CC#C#c                 2    t               j                         S )z!Get default identities structure.)r	   
model_dumprD   s    r"   rO   z%ConfigManager._get_default_identities   s    |&&((r$   c                 d   | j                   j                         s| j                         S 	 t        | j                   d      5 }t	        j
                  |      }| j                  |      cddd       S # 1 sw Y   yxY w# t        $ r(}t        d|        | j                         cY d}~S d}~ww xY w)z;Load option profiles from file (shareable, no credentials).rG   NzError loading profiles: )	r?   rH   _get_default_profilesrI   rJ   rK   _migrate_profilesrN   rM   )rD   rP   rC   rS   s       r"   rB   zConfigManager._load_profiles   s    !!((*--//	0d((#.!99Q<--h78 8..  	0,QC01--//	0s;   A> &A2(	A> 2A;7A> ;A> >	B/B*$B/*B/c                 8    dddddddi dddddd	d
i ddi dS )z+Get default profiles structure (shareable).   )music_volumeambience_volumeFEnglish)mute_global_chatmute_table_chat'include_language_filters_for_table_chatchat_input_languagelanguage_subscriptionsT)invert_multiline_enter_behaviorplay_typing_soundsalwaysneverr   start_as_visiblestart_with_passworddefault_password_textcreation_notifications)audiosocial	interfacelocal_table)client_options_defaultsserver_options rV   s    r"   rX   z#ConfigManager._get_default_profiles   sT     +-D(-',?D+4.0 8=*.
 )1+2-/.0 (* !-
 	
r$   rC   c                 6   d}| j                  |      rd}| j                  |      rd}| j                  |      rd}| j                  |      rd}| j	                  |      rd}| j                  |      rd}|r"|| _        | j                          t        d       |S )zMigrate profiles to fix data issues.

        Args:
            profiles: The loaded profiles dictionary

        Returns:
            Migrated profiles dictionary
        FTz.Profile migration completed and saved to disk.)	_migrate_server_options_ensure_server_options#_fix_default_language_subscriptions"_fix_server_language_subscriptions _migrate_default_table_creations_migrate_server_table_creationsrC   save_profilesrM   )rD   rC   
needs_saves      r"   rY   zConfigManager._migrate_profiles   s     
''1J&&x0J33H=J228<J00:J//9J $DM BCr$   c                     d|vsd|v ryi |d<   |d   j                         D ]  \  }}d|v s|d   s|d   |d   |<    |d= t        d       y)Nserversrr   Foptions_overridesz2Migrated 'servers' to 'server_options' in profilesT)itemsrM   )rD   rC   	server_idserver_infos       r"   ru   z%ConfigManager._migrate_server_options   sz    H$(8H(D%'!"&.y&9&?&?&A"I{"k1kBU6V8CDW8X)*95 'B YBCr$   c                     d|v ryi |d<   y)Nrr   FTrs   )rD   rC   s     r"   rv   z$ConfigManager._ensure_server_options   s    x'%'!"r$   c                     |j                  d      }t        |t              sy|j                  d      }t        |t              syd}|| j                  |dd      z  }|S )Nrq   Frn   zdefault profiledefault)prefix)getr   r,   _fix_language_subs_in_profile)rD   rC   defaultsrn   changeds        r"   rw   z1ConfigManager._fix_default_language_subscriptions   sa    << 9:(D)h'&$'455f>OXa5bbr$   c                     d}|j                  di       j                         D ]P  \  }}t        |t              s|j                  d      }t        |t              s9| j	                  |d|       sOd}R |S )NFrr   rn   zserver T)r   r   r   r,   r   )rD   rC   r   r   	overridesrn   s         r"   rx   z0ConfigManager._fix_server_language_subscriptions  sw    $,LL1A2$F$L$L$N Iyi.]]8,Ffd+11&GI;:OP %O r$   rn   labelr   c                     d}|j                  d      }t        |t              r(d|v r$|j                  d      |d<   d}t	        d|        |j                  d      }|dk(  rd|d<   d}t	        d|        |S )	NFrc   CheckCzechTz6Migrated language subscription: 'Check' -> 'Czech' in rb   z4Migrated chat_input_language: 'Check' -> 'Czech' in )r   r   r,   r+   rM   )rD   rn   r   r   r   	lang_subs	chat_langs          r"   r   z+ConfigManager._fix_language_subs_in_profile  s     JJ78	i&7i+?!*w!7IgGJ5'RSJJ45	,3F()GHPQr$   c                     |j                  d      }t        |t              rd|vry|j                  d      }| j	                  |j                  di       |      |d<   t        d       y)Nrq   table_creationsFrp   zUMigrated 'table_creations' -> 'local_table/creation_notifications' in default profileT)r   r   r,   r+   _build_local_table_migrationrM   )rD   rC   r   table_creations_values       r"   ry   z.ConfigManager._migrate_default_table_creations  sn    << 9:(D)->h-N (-> ?"&"C"CLL+!#
 	c	
 r$   c                    d}|j                  di       j                         D ]`  \  }}t        |t              rd|vr|j	                  d      }| j                  |j                  di       |      |d<   t        d|        d}b |S )NFrr   r   rp   zMMigrated 'table_creations' -> 'local_table/creation_notifications' in server T)r   r   r   r,   r+   r   rM   )rD   rC   r   r   r   r   s         r"   rz   z-ConfigManager._migrate_server_table_creations,  s    $,LL1A2$F$L$L$N Iyi.2C92T$-MM2C$D!'+'H'HmR0%(Im$ _`i_jk G %O r$   rp   r   c                     | j                  dd      | j                  dd      | j                  dd      |d}| j                         D ]  \  }}||vs|||<    |S )Nri   rf   rj   rg   rk   r   rh   )r   r   )rp   r   new_local_tablekeyr/   s        r"   r   z*ConfigManager._build_local_table_migration<  sn    
 !,0BH M#.??3H'#R%0__5Lb%Q&;	
 &++-JC/)',$ . r$   Tbaseoverrideoverride_winsc                 2   | j                  |      }|j                         D ]r  \  }}||vr| j                  |      ||<   t        |t              r-t        ||   t              r| j	                  ||   ||      ||<   \|s_| j                  |      ||<   t |S )a  Deep merge two dictionaries with configurable precedence.

        Supports infinite nesting depth.

        Args:
            base: Base dictionary
            override: Dictionary to merge into base
            override_wins: If True, override values take precedence on conflicts.
                           If False, base values take precedence (override fills missing keys only).

        Returns:
            Merged dictionary
        )
_deep_copyr   r   r,   _deep_merge)rD   r   r   r   resultr   r/   s          r"   r   zConfigManager._deep_mergeK  s      &"..*JC& "ooe4sE4(ZsT-J"..vc{E=Qs"ooe4s + r$   c                 "   	 | j                   j                  dd       t        | j                  d      5 }t	        j
                  | j                  |d       ddd       y# 1 sw Y   yxY w# t        $ r}t        d|        Y d}~yd}~ww xY w)zSave identities to file.Tparentsexist_okw   indentNzError saving identities: )	r;   mkdirrI   r>   rJ   dumprA   rN   rM   rD   rP   rS   s      r"   rL   zConfigManager.save_identitiesh  sr    	3NN   =d**C0A		$//1Q7 100 	3-aS122	34   3A- #A!A- !A*&A- *A- -	B6B		Bc                 "   	 | j                   j                  dd       t        | j                  d      5 }t	        j
                  | j                  |d       ddd       y# 1 sw Y   yxY w# t        $ r}t        d|        Y d}~yd}~ww xY w)zSave option profiles to file.Tr   r   r   r   NzError saving profiles: )	r;   r   rI   r?   rJ   r   rC   rN   rM   r   s      r"   r{   zConfigManager.save_profiless  sr    	1NN   =d((#.!		$--15 /.. 	1+A3/00	1r   c                 D    | j                          | j                          y)z"Save both identities and profiles.N)rL   r{   rV   s    r"   savezConfigManager.save~  s    r$   c                 8    | j                   j                  d      S )z Get ID of last connected server.last_server_idrA   r   rV   s    r"   get_last_server_idz ConfigManager.get_last_server_id  s    ""#344r$   r   c                 L    | j                  |      }|r|j                  d      S y)zGet ID of last used account for a server.

        Args:
            server_id: Server ID

        Returns:
            Account ID or None if not set
        last_account_idNget_server_by_idr   rD   r   servers      r"   get_last_account_idz!ConfigManager.get_last_account_id  s*     &&y1::/00r$   c                 >    | j                   d   j                  |      S )zGet server info by ID.

        Args:
            server_id: Unique server ID

        Returns:
            Server info dict or None if not found
        r~   r   rD   r   s     r"   r   zConfigManager.get_server_by_id  s     y)--i88r$   c                      | j                   d   S )z]Get all servers.

        Returns:
            Dict mapping server_id to server info
        r~   )rA   rV   s    r"   get_all_serverszConfigManager.get_all_servers  s     y))r$   namehostportnotesc                     t        ||||      }|j                  }|j                         | j                  d   |<   | j	                          |S )zAdd a new server.

        Args:
            name: Server display name
            host: Server host address
            port: Server port
            notes: Optional notes about the server

        Returns:
            New server ID
        )r   r   r   r   r~   )r
   r   rU   rA   rL   )rD   r   r   r   r   r   r   s          r"   
add_serverzConfigManager.add_server  sM    $ T4uE$$	060A0A0C	"9-r$   c                     || j                   d   vry| j                   d   |   }|||d<   |||d<   |||d<   |||d<   | j                          y)a  Update server information.

        Args:
            server_id: Server ID
            name: New server name (if provided)
            host: New host address (if provided)
            port: New port (if provided)
            notes: New notes (if provided)
        r~   Nr   r   r   r   rA   rL   )rD   r   r   r   r   r   r   s          r"   update_serverzConfigManager.update_server  ss    " DOOI66+I6!F6N!F6N!F6N#F7Or$   c                     || j                   d   v rN| j                   d   |= | j                   j                  d      |k(  rd| j                   d<   | j                          yy)zhDelete a server and all its accounts.

        Args:
            server_id: Server ID to delete
        r~   r   N)rA   r   rL   r   s     r"   delete_serverzConfigManager.delete_server  s^     	22	*95""#34	A48 01  " 3r$   c                 N    | j                  |      }|r|j                  dd      S y)zGet display name for a server.

        Args:
            server_id: Server ID

        Returns:
            Display name
        r   zUnknown Serverr   r   s      r"   get_server_display_namez%ConfigManager.get_server_display_name  s,     &&y1::f&677r$   c                    | j                  |      }|sy|j                  dd      }|j                  dd      }d|v rA|j                  d      d   j                         }|j                  dd      d   }| d| d	| S d
| d	| S )zBuild WebSocket URL for a server.

        Args:
            server_id: Server ID

        Returns:
            WebSocket URL or None if server not found
        Nr   r   r   i@  z://r   r   :zws://)r   r   r   lower)rD   r   r   r   r   scheme	host_parts          r"   get_server_urlzConfigManager.get_server_url  s     &&y1zz&"%zz&$' D=ZZ&q)//1F

5!,Q/IXS1TF334&$((r$   c                 B    || j                   d<   | j                          y)zWSet the last connected server.

        Args:
            server_id: Server ID
        r   Nr   r   s     r"   set_last_serverzConfigManager.set_last_server  s     -6()r$   c                 L    | j                  |      }|r|j                  d      S y)z1Return trusted certificate metadata for a server.trusted_certificateNr   r   s      r"   get_trusted_certificatez%ConfigManager.get_trusted_certificate  s(    &&y1::344r$   	cert_infoc                 V    | j                  |      }|sy||d<   | j                          y)z0Store trusted certificate metadata for a server.Nr   r   rL   )rD   r   r   r   s       r"   set_trusted_certificatez%ConfigManager.set_trusted_certificate&  s/    &&y1(1$%r$   c                 `    | j                  |      }|syd|v rd|d<   | j                          yy)z0Remove stored certificate metadata for a server.Nr   r   r   s      r"   clear_trusted_certificatez'ConfigManager.clear_trusted_certificate.  s<    &&y1 F*,0F()  " +r$   c                 P    | j                  |      }|r|j                  di       S i S )zGet all accounts for a server.

        Args:
            server_id: Server ID

        Returns:
            Dict mapping account_id to account info
        accountsr   r   s      r"   get_server_accountsz!ConfigManager.get_server_accounts9  s-     &&y1::j"--	r$   
account_idc                 l    | j                  |      }|r!|j                  di       j                  |      S y)zGet account info by ID.

        Args:
            server_id: Server ID
            account_id: Account ID

        Returns:
            Account info dict or None if not found
        r   Nr   rD   r   r   r   s       r"   get_account_by_idzConfigManager.get_account_by_idG  s6     &&y1::j"-11*==r$   usernamepasswordemailrefresh_tokenrefresh_expires_atc                 "   || j                   d   vryt        ||||||      }|j                  }	d| j                   d   |   vri | j                   d   |   d<   |j                         | j                   d   |   d   |	<   | j	                          |	S )aM  Add a new account to a server.

        Args:
            server_id: Server ID
            username: Account username
            password: Account password
            email: Optional email address
            notes: Optional notes about the account

        Returns:
            New account ID, or None if server not found
        r~   N)r   r   r   r   r   r   r   )rA   r   r   rU   rL   )
rD   r   r   r   r   r   r   r   accountr   s
             r"   add_accountzConfigManager.add_accountV  s    . DOOI66'1
 ''
T__Y7	BB@BDOOI&y1*=HOHZHZH\	"9-j9*Er$   c	                     | j                  ||      }	|	sy|||	d<   |||	d<   |||	d<   |||	d<   |||	d<   |||	d<   | j                          y)a7  Update account information.

        Args:
            server_id: Server ID
            account_id: Account ID
            username: New username (if provided)
            password: New password (if provided)
            email: New email address (if provided)
            notes: New notes (if provided)
        Nr   r   r   r   r   r   )r   rL   )
rD   r   r   r   r   r   r   r   r   r   s
             r"   update_accountzConfigManager.update_account  s    * ((J?"*GJ"*GJ$'4GO$),>G()$GG$GGr$   c                     | j                  |      }|rE||j                  di       v r0|d   |= |j                  d      |k(  rd|d<   | j                          yyy)zDelete an account from a server.

        Args:
            server_id: Server ID
            account_id: Account ID to delete
        r   r   N)r   r   rL   r   s       r"   delete_accountzConfigManager.delete_account  sf     &&y1jFJJz2$>>z":.zz+,
:,0()  " ?6r$   c                 r    || j                   d<   | j                  |      }|r||d<   | j                          y)zSet the last used account for a server.

        Args:
            server_id: Server ID
            account_id: Account ID
        r   r   N)rA   r   rL   r   s       r"   set_last_accountzConfigManager.set_last_account  s=     -6()&&y1(2F$%r$   c                     | j                  | j                  d         }|rB|| j                  j                  di       v r$| j                  d   |   }| j                  ||      }|S )zGet client options for a server (defaults + overrides).

        Args:
            server_id: Server ID, or None for just defaults

        Returns:
            Complete options dict with overrides applied
        rq   rr   )r   rC   r   r   )rD   r   optionsr   s       r"   get_client_optionsz ConfigManager.get_client_options  se     //$--0I"JK dmm&7&78H"&MM&67	BI&&w	:Gr$   Fr   r   r/   r   c                    || j                   d   }n<d| j                   vri | j                   d<   | j                   d   j                  |i       }t        ||||      }|r| j                          yy)ax  Set a client option (either default or server-specific override).

        Args:
            key_path: Path to the option (e.g., "audio/music_volume", "social/language_subscriptions/English")
            value: Option value
            server_id: Server ID for override, or None for default
            create_mode: If True, create intermediate dictionaries as needed
        Nrq   rr   r   )rC   
setdefaultr2   r{   )rD   r   r/   r   r   targetsuccesss          r"   set_client_optionzConfigManager.set_client_option  sv      ]]#<=F  t}}424./]]#34??	2NF"68UT  r$   r3   r4   c                    || j                   j                  di       vry| j                   d   |   }t        |||      }|r| j                          yy)a  Clear a server-specific override (revert to default).

        Args:
            server_id: Server ID
            key_path: Path to the option (e.g., "audio/music_volume")
            delete_empty_layers: If True, delete intermediate dictionaries if empty
        rr   Nr3   )rC   r   r8   r{   )rD   r   r   r4   r   r   s         r"   clear_server_overridez#ConfigManager.clear_server_override  s]     DMM--.>CCMM"23I>	'x5H
   r$   r1   c                     t        |t              r3|j                         D ci c]  \  }}|| j                  |       c}}S t        |t              r|D cg c]  }| j                  |       c}S |S c c}}w c c}w )z'Deep copy a nested dict/list structure.)r   r,   r   r   list)rD   r1   kvitems        r"   r   zConfigManager._deep_copy  so    c4 69iikBkdaAtq))kBBT"69:cdDOOD)c::J	 C:s   A5A;)N)T)r   )NNNN)r   r   r   N)NNNNNN)5__name__
__module____qualname____doc__r   r   rE   r   r   r   r@   rO   rB   rX   rY   boolru   rv   rw   rx   r   ry   rz   staticmethodr   r   rL   r{   r   r   r   r   r   intr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rs   r$   r"   r:   r:   o   sy   .(4. . .$sCx. .$)c3h )0S#X 0
tCH~ 
6$sCx. T#s(^ @	S#X 	4 	tCH~ $ 	DcN 	t 	
4S> 
d 
 HL38n-0:=*	 c3h D S#X 4   #s(^<@cN	c3h  UYcN.238nMQ	c3h:	3	15HSM 5S Xc] 	9# 	9(4S>2J 	9*c4S>&9!: *   	
  
6 #""# sm sm	
 sm }>#s #     ) ) )0  $sCx.9Q  c3h TX #3 #4 #S T#tCH~:M5N 3 C HTRUWZRZ^D\ * ,0(( (
 ( ( ( ( %SM( 
#(\ #'"&'+,0##%% % 3-	%
 3-%  }% %SM% }% }%N# # ## 3 HSM T#s(^ . $(	! "!! ! C=	! !< MQ!!(+!EI!*c c r$   r:   )r  rJ   uuidpathlibr   typingr   r   r   r   r   config_schemasr	   r
   r   r   r,   r   tupler  r#   r2   r8   r:   rs   r$   r"   <module>r     s       , ,  O O Y^ 4 C< QU @ MR!$eEI	2 NR##!$e#FJ#	#L`
 `
r$   