import { Result , Ok , Err } from 'ts-results' ; internal const $fromString = Symbol ( ) ; /**
 * This type only refers to the stringified Identifier
 * person:...
 * group:...
 * post:...
 * post_iv:...
 * ec_key:...
 */ type Identifiers = 'person' | 'group' | 'post' | 'post_iv' | 'ec_key' ; const fromStringCache = new Map < string , Identifier | undefined | null > ( ) ; interface IdentifierFromString { /**
     * fromString("some string", ProfileIdentifier)
     * fromString("some string", ECKeyIdentifier)
     */ < T extends Identifier > ( id , c ) : Result < T , TypeError > ; /**
     * fromString("some string")
     */ ( id ) : Result < Identifier , TypeError > ; } const fromString = ( id , constructor ? ) : Result < Identifier , TypeError > => { let result = null ; if ( id instanceof Identifier ) result = id ; else { const [ type , ... rest ] = id . split ( ':' ) as [ Identifiers , string ] ; if ( fromStringCache . has ( id ) ) result = fromStringCache . get ( id ) ; else if ( type === 'person' ) result = ProfileIdentifier [ $fromString ] ( rest . join ( ':' ) ) ; else if ( type === 'group' ) result = GroupIdentifier [ $fromString ] ( rest . join ( ':' ) ) ; else if ( type === 'post' ) result = PostIdentifier [ $fromString ] ( rest . join ( ':' ) ) ; else if ( type === 'post_iv' ) result = PostIVIdentifier [ $fromString ] ( rest . join ( ':' ) ) ; else if ( type === 'ec_key' ) result = ECKeyIdentifier [ $fromString ] ( rest . join ( ':' ) ) ; else return Err ( new TypeError ( 'Unreachable case:' + type ) ) ; fromStringCache . set ( id , result ) ; } const err = Err ( new TypeError ( `Can't cast to Identifier. Expected: ${ constructor ?. name || 'Any Identifier' }, Try to convert from string: ${ id }` ) ) ; if ( ! constructor ) return result ? Ok ( result ) : err ; else if ( result instanceof constructor ) return Ok ( result ) ; else return err ; } ; export abstract class Identifier { static equals ( a ? , b ? ) { return ! ! a ?. equals ( b ) ; } public equals ( other ) { if ( ! other ) return false ; return this === other || this . toText ( ) === other . toText ( ) ; } abstract toText ( ) ; static fromString : IdentifierFromString = fromString as any ; static IdentifiersToString ( a , isOrderImportant = false ) { const ax = a . map ( ( x ) => x . toText ( ) ) ; if ( ! isOrderImportant ) { ax . sort ( ) ; } return ax . join ( ',' ) ; } } export class ProfileIdentifier extends Identifier { static getUserName ( x ) { if ( typeof x === 'string' ) { if ( [ '' , '$unknown' ] . includes ( x ) ) return null ; return x ; } else { if ( x . isUnknown ) return null ; return x . userId ; } } static readonly unknown = new ProfileIdentifier ( 'localhost' , '$unknown' ) ; get isUnknown ( ) { return this . equals ( ProfileIdentifier . unknown ) ; } param network param userId constructor ( public readonly network , public readonly userId ) { super ( ) ; noSlash ( network ) ; noSlash ( userId ) ; } toText ( ) { return `person:${ this . network }/${ this . userId }` ; } friendlyToText ( ) { return `${ this . userId }@${ this . network }` ; } static [ $fromString ] ( str ) { const [ network , userId ] = str . split ( '/' ) ; if ( ! network || ! userId ) return null ; return new ProfileIdentifier ( network , userId ) ; } } export class GroupIdentifier extends Identifier { static getFriendsGroupIdentifier ( who , groupId ) { return new GroupIdentifier ( who . network , who . userId , groupId ) ; } constructor ( public readonly network , public readonly virtualGroupOwner , public readonly groupID ) { super ( ) ; noSlash ( network ) ; noSlash ( groupID ) ; if ( virtualGroupOwner === '' ) this . virtualGroupOwner = null ; } get ownerIdentifier ( ) : ProfileIdentifier | null { if ( this . virtualGroupOwner === null ) return null ; return new ProfileIdentifier ( this . network , this . virtualGroupOwner ) ; } toText ( ) { return 'group:' + [ this . network , this . virtualGroupOwner , this . groupID ] . join ( '/' ) ; } get isReal ( ) { return ! this . virtualGroupOwner ; } get isVirtual ( ) { return ! ! this . virtualGroupOwner ; } static [ $fromString ] ( str ) { const [ network , belongs , groupID ] = str . split ( '/' ) ; if ( ! network || ! groupID ) return null ; return new GroupIdentifier ( network , belongs , groupID ) ; } } export class PostIdentifier < T extends Identifier = Identifier > extends Identifier { static readonly unknown = new PostIdentifier ( ProfileIdentifier . unknown , '$unknown' ) ; get isUnknown ( ) { return this . equals ( PostIdentifier . unknown ) ; } /**
     * If identifier is a PostIdentifier, that means this post is bound with other post in some kind
     * e.g. a comment.
     */ constructor ( public readonly identifier , public readonly postId ) { super ( ) ; noSlash ( postId ) ; } toText ( ) { return `post:${ this . postId }/${ this . identifier . toText ( ) }` ; } static [ $fromString ] ( str ) { const [ postId , ... identifier ] = str . split ( '/' ) ; const id = Identifier . fromString ( identifier . join ( '/' ) ) ; if ( id . err || ! postId ) return null ; return new PostIdentifier ( id . val , postId ) ; } } export class PostIVIdentifier extends Identifier { constructor ( public readonly network , public readonly postIV ) { super ( ) ; } toText ( ) { return `post_iv:${ this . network }/${ this . postIV . replace ( /\//g , '|' ) }` ; } static [ $fromString ] ( str ) { const [ network , iv ] = str . split ( '/' ) ; if ( ! network || ! iv ) return null ; return new PostIVIdentifier ( network , iv ) ; } } /**
 * This class identify the point on an EC curve.
 * ec_key:secp256k1/CompressedPoint
 */ export class ECKeyIdentifier extends Identifier { public readonly type = 'ec_key' ; constructor ( public readonly curve , public readonly compressedPoint ) { super ( ) ; } /** This property might be filled with old data, which means you MUST NOT change the property name */ private declare readonly encodedCompressedKey : string ; toText ( ) { const normalized = this . encodedCompressedKey ?? this . compressedPoint . replace ( /\//g , '|' ) ; return `ec_key:${ this . curve }/${ normalized }` ; } static [ $fromString ] ( str ) { const [ curve , point ] = str . split ( '/' ) ; if ( curve !== 'secp256k1' ) return null ; return new ECKeyIdentifier ( curve , point ) ; } } export type PersonaIdentifier = ECKeyIdentifier ; export const PersonaIdentifier = [ ECKeyIdentifier ] ; /**
 * Because "/" is used to split parts in identifier
 * we should reject the "/"
 *
 * If you want to use it, you must first convert to something other
 */ function noSlash ( str ? ) { if ( ! str ) return ; if ( str . includes ( '/' ) ) throw new TypeError ( 'Cannot contain / in a part of identifier' ) ; }